Introduction
Generating simulations and then using the thymus ageing data to test differential abundance testing on a graph/tree.
library(ggplot2)
library(edgeR)
library(igraph)
library(SingleCellExperiment)
library(scran)
library(scater)
library(irlba)
library(ggthemes)
library(ggsci)
library(cydar)
library(mvtnorm)
library(umap)
library(reshape2)
Simulation 1
I’ll start by simulating 3 clouds of points in \(\mathbb{R}^{n}\), each consisting of points from 2 pools, A & B. Each cloud of points will be composed of:
- A: 10%, B: 90%
- A: 90%, B: 10%
- A: 50%, B: 50%
I will then perform a PCA on the matrix of these points and construct a KNN-graph, mimicing the standard workflow of many scRNA-seq analyses.
set.seed(42)
r.n <- 1000
n.dim <- 50
block1.cells <- 1200
# select a set of eigen values for the covariance matrix of each block, say 50 eigenvalues?
block1.eigens <- sapply(1:n.dim, FUN=function(X) rexp(n=1, rate=abs(runif(n=1, min=0, max=50))))
block1.eigens <- block1.eigens[order(block1.eigens)]
block1.p <- qr.Q(qr(matrix(rnorm(block1.cells^2, mean=4, sd=0.01), block1.cells)))
block1.sigma <- crossprod(block1.p, block1.p*block1.eigens)
block1.gex <- abs(rmvnorm(n=r.n, mean=rnorm(n=block1.cells, mean=2, sd=0.01), sigma=block1.sigma))
block2.cells <- 1200
# select a set of eigen values for the covariance matrix of each block, say 50 eigenvalues?
block2.eigens <- sapply(1:n.dim, FUN=function(X) rexp(n=1, rate=abs(runif(n=1, min=0, max=50))))
block2.eigens <- block2.eigens[order(block2.eigens)]
block2.p <- qr.Q(qr(matrix(rnorm(block2.cells^2, mean=4, sd=0.01), block2.cells)))
block2.sigma <- crossprod(block2.p, block2.p*block2.eigens)
block2.gex <- abs(rmvnorm(n=r.n, mean=rnorm(n=block2.cells, mean=4, sd=0.01), sigma=block2.sigma))
block3.cells <- 1250
# select a set of eigen values for the covariance matrix of each block, say 50 eigenvalues?
block3.eigens <- sapply(1:n.dim, FUN=function(X) rexp(n=1, rate=abs(runif(n=1, min=0, max=50))))
block3.eigens <- block3.eigens[order(block3.eigens)]
block3.p <- qr.Q(qr(matrix(rnorm(block3.cells^2, mean=4, sd=0.01), block3.cells)))
block3.sigma <- crossprod(block3.p, block3.p*block3.eigens)
block3.gex <- abs(rmvnorm(n=r.n, mean=rnorm(n=block3.cells, mean=5, sd=0.01), sigma=block3.sigma))
sim1.gex <- do.call(cbind, list("b1"=block1.gex, "b2"=block2.gex, "b3"=block3.gex))
sim1.pca <- prcomp_irlba(t(sim1.gex), n=50, scale.=TRUE, center=TRUE)
pairs(sim1.pca$x[, c(1:5)])

I’ll use the reduced dimensions here to compute a KNN-graph and visualise it using a Fructerman-Reingold layout.
set.seed(42)
sim1.knn <- buildKNNGraph(x=sim1.pca$x[, c(1:30)], k=21, d=NA, transposed=TRUE)
sim1.fr.layout <- layout_with_fr(sim1.knn)
plot(sim1.knn, layout=sim1.fr.layout, vertex.frame.color='skyblue', vertex.color='skyblue', vertex.label.color='black',
vertex.label.family='Helvetica', edge.color='grey60', vertex.label.cex=0.9,
vertex.label.dist=1, edge.arrow.size=0.2)

Also a UMAP layout.
set.seed(42)
stem.ta.umap <- umap(sim1.pca$x[, c(1:30)],
n_components=2,
n_neighbors=21, metric='euclidean',
init='random', min_dist=0.1)
plot(stem.ta.umap$layout, col=c(rep("red", block1.cells), rep("skyblue", block2.cells), rep("orange", block3.cells)),
xlab="UMAP 1", ylab="UMAP 2")

Within each of these clouds of points I will randomly label in the proportions above.
set.seed(42)
block1.cond <- rep("A", block1.cells)
block1.a <- sample(1:block1.cells, size=floor(block1.cells*0.9))
block1.b <- setdiff(1:block1.cells, block1.a)
block1.cond[block1.b] <- "B"
block2.cond <- rep("A", block2.cells)
block2.a <- sample(1:block2.cells, size=floor(block2.cells*0.05))
block2.b <- setdiff(1:block2.cells, block2.a)
block2.cond[block2.b] <- "B"
block3.cond <- rep("A", block3.cells)
block3.a <- sample(1:block3.cells, size=floor(block3.cells*0.5))
block3.b <- setdiff(1:block3.cells, block3.a)
block3.cond[block3.b] <- "B"
meta.df <- data.frame("Block"=c(rep("B1", block1.cells), rep("B2", block2.cells), rep("B3", block3.cells)),
"Condition"=c(block1.cond, block2.cond, block3.cond),
"Replicate"=c(rep("R1", floor(block1.cells*0.33)), rep("R2", floor(block1.cells*0.33)),
rep("R3", block1.cells-(2*floor(block1.cells*0.33))),
rep("R1", floor(block2.cells*0.33)), rep("R2", floor(block2.cells*0.33)),
rep("R3", block2.cells-(2*floor(block2.cells*0.33))),
rep("R1", floor(block3.cells*0.33)), rep("R2", floor(block3.cells*0.33)),
rep("R3", block3.cells-(2*floor(block3.cells*0.33)))))
meta.df <- cbind(meta.df, stem.ta.umap$layout)
colnames(meta.df) <- c("Block", "Condition", "Replicate", "UMAP1", "UMAP2")
# define a "sample" as teh combination of condition and replicate
meta.df$Sample <- paste(meta.df$Condition, meta.df$Replicate, sep="_")
meta.df$Vertex <- c(1:nrow(meta.df))
ggplot(meta.df, aes(x=UMAP1, y=UMAP2)) +
geom_point(aes(colour=Block, shape=Replicate)) +
theme_clean() +
scale_colour_npg() +
facet_wrap(~Condition) +
guides(colour=guide_legend(override.aes=list(size=3)),
shape=guide_legend(override.aes=list(size=3)))

Using these simulated data we can define lots of neighbourhoods across the graph to create a counts matrix of neighbourhoods vs conditions in a similar way as cydar. I’ll start with random vertices in the graph making up 5% of all points and extract the graph neighborhoods.
# randomly select vertices in the graph
n.hood <- 0.05
random.vertices <- sample(V(sim1.knn), size=floor(n.hood*length(V(sim1.knn))))
# loop over random vertices and count the number of cells in each
vertex.list <- sapply(1:length(random.vertices), FUN=function(X) neighbors(sim1.knn, v=random.vertices[X]))
hist(unlist(lapply(vertex.list, length)), 100, main="Histogram of neighbors", xlab="Neighbourhood size")

For each neighbourhood I’ll count the number of cells in each, determined by the experimental design, i.e. replicate, condition and block.
quant_neighbourhood <- function(graph, meta, sample.column='Sample', sample.vertices=0.25, seed=42){
set.seed(seed)
# define a set of vertices and neihbourhood centers - extract the neihbourhoods of these cells
random.vertices <- sample(V(graph), size=floor(sample.vertices*length(V(graph))))
vertex.list <- sapply(1:length(random.vertices), FUN=function(X) neighbors(graph, v=random.vertices[X]))
count.matrix <- matrix(0L, ncol=length(unique(meta[, sample.column])), nrow=length(vertex.list))
colnames(count.matrix) <- unique(meta[, sample.column])
for(x in seq_along(1:length(vertex.list))){
v.x <- vertex.list[[x]]
for(i in seq_along(1:length(unique(meta[, sample.column])))){
i.s <- unique(meta[, sample.column])[i]
i.s.vertices <- intersect(v.x, meta[meta[, sample.column] == i.s, ]$Vertex)
count.matrix[x, i] <- length(i.s.vertices)
}
}
rownames(count.matrix) <- c(1:length(vertex.list))
return(count.matrix)
}
# define a set of vertices and neihbourhood centers - extract the neihbourhoods of these cells
set.seed(42)
random.vertices <- sample(V(sim1.knn), size=floor(n.hood*length(V(sim1.knn))))
vertex.list <- sapply(1:length(random.vertices), FUN=function(X) neighbors(sim1.knn, v=random.vertices[X]))
count.matrix <- matrix(0L, ncol=length(unique(meta.df[, "Sample"])), nrow=length(vertex.list))
colnames(count.matrix) <- unique(meta.df[, "Sample"])
for(x in seq_along(1:length(vertex.list))){
v.x <- vertex.list[[x]]
for(i in seq_along(1:length(unique(meta.df[, "Sample"])))){
i.s <- unique(meta.df[, "Sample"])[i]
i.s.vertices <- intersect(v.x, meta.df[meta.df[, "Sample"] == i.s, ]$Vertex)
count.matrix[x, i] <- length(i.s.vertices)
}
}
rownames(count.matrix) <- c(1:length(vertex.list))
sim1.counts <- quant_neighbourhood(graph=sim1.knn, meta=meta.df, sample.column='Sample', sample.vertices=n.hood)
sample.meta <- data.frame("Condition"=c(rep("A", 3), rep("B", 3)),
"Replicate"=rep(c("R1", "R2", "R3"), 2))
sample.meta$Sample <- paste(sample.meta$Condition, sample.meta$Replicate, sep="_")
rownames(sample.meta) <- sample.meta$Sample
# sim1.model <- model.matrix(~ 0 + Condition, data=sample.meta)
sim1.model <- model.matrix(~ Condition, data=sample.meta)
head(sim1.model)
(Intercept) ConditionB
A_R1 1 0
A_R2 1 0
A_R3 1 0
B_R1 1 1
B_R2 1 1
B_R3 1 1
I have a model matrix and counts matrix - let’s test edgeR on these.
sim1.dge <- DGEList(sim1.counts[, rownames(sim1.model)], lib.size=log(colSums(sim1.counts[, rownames(sim1.model)])))
sim1.dge <- estimateDisp(sim1.dge, sim1.model)
sim1.fit <- glmQLFit(sim1.dge, sim1.model, robust=TRUE)
# sim1.contrast <- makeContrasts(ConditionA - ConditionB, levels=sim1.model)
# sim1.res <- glmQLFTest(sim1.fit, contrast=sim1.contrast)
sim1.res <- as.data.frame(topTags(glmQLFTest(sim1.fit, coef=2), sort.by='none', n=Inf))
sim1.res$Sig <- as.factor(as.numeric(sim1.res$FDR <= 0.01))
sim1.res$Neighbourhood <- as.numeric(rownames(sim1.res))
# control the spatial FDR
qvals <- sim1.res$PValue
is.sig <- qvals <= 0.01
summary(is.sig)
Mode FALSE TRUE
logical 59 123
This indicates that 123 of the neighbour hoods are significant before spatial FDR correction. The implementation in Cydar relies on having the median marker intensities for each hypersphere. What is the equivalent positional information here? What about drawing a new graph for each neighborhood using the igraph::induced_subgraph function, then calculate the connectivity of this new sub-graph as a measure of the neighborhood density?
# this creates a sub-graph for each of the random vertices
test.subgraphs <- lapply(1:length(random.vertices),
FUN=function(X) induced_subgraph(sim1.knn, vertex.list[[X]]))
# now loop over these sub-graphs to calculate the connectivity - this seems a little slow...
test.connect <- lapply(test.subgraphs, FUN=function(EG) vertex_connectivity(EG))
Vertex connectivity is slow to compute - edge connectivity might be faster and has the useful property that:
\[k(G) \leq \lambda(G) \] That is, the vertex connectivity \(k(G)\) is \(\leq\) than the edge connectivity \(\lambda(G)\). In this instance the edge-connectivity is an upper-bound on the neighborhood connectivity, and thus an upper bound on the density, leading to a slightly lower weighting for the spatial FDR adjustment.
# now loop over these sub-graphs to calculate the connectivity - this seems a little slow...
edge.connect <- lapply(test.subgraphs, FUN=function(EG) edge_connectivity(EG))
The edge-connectivity takes ~10% of the time. How do they compare?
plot(unlist(test.connect), unlist(edge.connect), main="Graph-connectivity measures", xlab="Vertex-connectivity", ylab="Edge-connectivity")

For the most part the vertex-connectivity and edge-connectivity for neighborhoods are in pretty good agreement. I’ll maybe include the option to use either as a function parameter.
graph_spatialFDR <- function(neighborhoods, graph, pvalues, connectivity='vertex'){
# input a set of neighborhoods as a list of graph vertices
# the input graph and the unadjusted GLM p-values
#' neighborhoods: list of vertices and their respective neighborhoods
#' graph: input kNN graph
#' pvalues: a vector of pvalues in the same order as the neighborhood indices
#' connectivity: character - edge or vertex to calculate neighborhood connectivity
# Discarding NA pvalues.
haspval <- !is.na(pvalues)
if (!all(haspval)) {
coords <- coords[haspval, , drop=FALSE]
pvalues <- pvalues[haspval]
}
# define the subgraph for each neighborhood then calculate the vertex connectivity for each
# this latter computation is quite slow - can it be sped up?
subgraphs <- lapply(1:length(neighborhoods[haspval]),
FUN=function(X) induced_subgraph(graph, neighborhoods[haspval][[X]]))
# now loop over these sub-graphs to calculate the connectivity - this seems a little slow...
if(connectivity == "vertex"){
connect <- lapply(subgraphs, FUN=function(EG) vertex_connectivity(EG))
} else if(connectivity == "edge"){
connect <- lapply(subgraphs, FUN=function(EG) edge_connectivity(EG))
} else{
errorCondition("connectivity option not recognised - must be either edge or vertex")
}
# use 1/connectivity as the weighting for the weighted BH adjustment from Cydar
w <- 1/unlist(connect)
# set Inf to 0
w[is.infinite(w)] <- 0
# Computing a density-weighted q-value.
o <- order(pvalues)
pvalues <- pvalues[o]
w <- w[o]
adjp <- numeric(length(o))
adjp[o] <- rev(cummin(rev(sum(w)*pvalues/cumsum(w))))
adjp <- pmin(adjp, 1)
if (!all(haspval)) {
refp <- rep(NA_real_, length(haspval))
refp[haspval] <- adjp
adjp <- refp
}
return(adjp)
}
start.time <- Sys.time()
sim1.spatialfdr <- graph_spatialFDR(neighborhoods=vertex.list, graph=sim1.knn, connectivity="edge",
pvalues=sim1.res[order(sim1.res$Neighbourhood), ]$PValue)
end.time <- Sys.time()
connect.time <- end.time - start.time
sim1.res$SpatialFDR[order(sim1.res$Neighbourhood)] <- sim1.spatialfdr
qvals <- sim1.spatialfdr
is.sig <- qvals <= 0.01
summary(is.sig)
Mode FALSE TRUE
logical 62 120
How do these compare to the standard FDR adjustment?
plot(-log10(sim1.res[order(sim1.res$Neighbourhood), ]$FDR),
-log10(sim1.spatialfdr), main="FDR comparison", xlab="-log10 FDR", ylab="-log10 Spatial FDR")

In this instance there is not a huge difference. That seems to work fairly well. Does it make sense though? i.e. are the changes in the graph in the expected regions? I’ll follow the Cydar workflow for this and project the neighbourhoods into a reduced dimensional space.
neighborhood_expression <- function(neighborhoods, data.set){
# I'll calculate the average value of each neighborhood for each of the n features in the data.set
neighbour.model <- matrix(0L, ncol=length(neighborhoods), nrow=ncol(data.set))
# neighbour.model <- sapply(1:length(neighborhoods), FUN=function(X) print(length(neighbour.model[, X])))
for(x in seq_along(1:length(neighborhoods))){
neighbour.model[neighborhoods[[x]], x] <- 1
}
neigh.exprs <- t(neighbour.model) %*% t(data.set)
neigh.exprs <- t(apply(neigh.exprs, 2, FUN=function(XP) XP/colSums(neighbour.model)))
return(neigh.exprs)
}
sim1.neighbour.exprs <- neighborhood_expression(vertex.list, sim1.gex)
Embed these hyperspheres with a PCA and UMAP.
sim1.neighbour.pca <- prcomp((t(sim1.neighbour.exprs)))
pairs(sim1.neighbour.pca$x[, c(1:5)])

set.seed(42)
neighbourhood.umap <- umap(sim1.neighbour.pca$x[, c(1:30)],
n_components=2,
n_neighbors=21, metric='euclidean',
init='random', min_dist=0.1)
plot(neighbourhood.umap$layout,
xlab="UMAP 1", ylab="UMAP 2")

We can overlay the DA testing on these neighbourhoods.
neighbor.df <- sim1.res[, c("logFC", "Neighbourhood", "SpatialFDR")]
neighbor.df <- do.call(cbind.data.frame, list(neighbor.df, as.data.frame(neighbourhood.umap$layout)))
colnames(neighbor.df) <- c("logFC", "Neighbourhood", "SpatialFDR", "UMAP1", "UMAP2")
neighbor.df$Sig <- as.numeric(neighbor.df$SpatialFDR <= 0.05)
ggplot(neighbor.df, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=neighbor.df[neighbor.df$Sig == 0, ],
colour='grey80', size=2) +
geom_point(data=neighbor.df[neighbor.df$Sig == 1, ],
aes(colour=logFC), size=4) +
theme_clean() +
scale_colour_gradient2(low="blue", mid="grey80", high="red")

In each neighbourhood, what is the most common condition or block of cells?
neighbour.list <- list()
for(x in seq_along(1:length(vertex.list))){
x.df <- meta.df[meta.df$Vertex %in% vertex.list[[x]], ]
x.rep <- names(table(x.df$Replicate))[which(table(x.df$Replicate) == max(table(x.df$Replicate)))]
if(length(x.rep) > 1){
x.rep <- sample(size=1, x.rep)
}
x.block <- names(table(x.df$Block))[which(table(x.df$Block) == max(table(x.df$Block)))]
if(length(x.block) > 1){
x.block <- sample(size=1, x.block)
}
x.condition <- names(table(x.df$Condition))[which(table(x.df$Condition) == max(table(x.df$Condition)))]
if(length(x.condition) > 1){
x.condition <- sample(size=1, x.condition)
}
neighbour.list[[x]] <- data.frame("Replicate"=x.rep, "Block"=x.block, "Condition"=x.condition, "Neighbourhood"=x)
}
neighbour.meta <- do.call(rbind.data.frame, neighbour.list)
neighbour.merge <- merge(neighbor.df, neighbour.meta, by='Neighbourhood')
neighbour.merge$Block <- ordered(neighbour.merge$Block,
levels=c("B1", "B2", "B3"))
neighbour.merge$Diff <- sign(neighbour.merge$logFC)
neighbour.merge$Diff[neighbour.merge$Sig == 0] <- 0
ggplot(neighbour.merge, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=neighbour.merge[, c("UMAP1", "UMAP2")],
colour='grey80', size=1) +
geom_point(data=neighbour.merge[neighbour.merge$Sig == 1, ],
aes(colour=logFC), size=4) +
theme_clean() +
scale_colour_gradient2(low="blue", mid="grey80", high="red") +
facet_wrap(~Block)

This doesn’t make sense - Block 3 shouldn’t have any DA neighbourhoods. Is this a compositional effect we’re seeing here? It’s strange that a random fluctuation would cause this - it must be incredibly sensitive.
table(neighbour.merge$Block, neighbour.merge$Diff)
-1 0 1
B1 66 0 0
B2 0 0 56
B3 3 57 0
Is this due to some weird global scaling differences or is this a compositional effect? In addition to the high false-positive rate, there are also sign differences where they should be concordant within a block of data points. For each neighbourhood, how much does it overlap with the over blocks? Intuitively I would have expected 0 as they are well separated, however, this might be a cause for all these false DA neighbourhoods.
NB: This was due to different vertices being sampled, so the results weren’t concordant - set the same seed Mike!!!!
The false-positive rate is a little high here, but at least the signs are the correct way around.
plot(x=sim1.res$logFC, y=-log10(sim1.res$SpatialFDR), xlab="log FC", ylab="Spatial FDR",
col=ifelse(sim1.res$Sig == 1, "red", "black"))

For each neighbourhood, I need to count the number of points in each block, as well as condition and replicate.
all.samps <- unique(paste(meta.df$Block, meta.df$Condition, meta.df$Replicate, sep="_"))
meta.df$All.Sample <- paste(meta.df$Block, meta.df$Condition, meta.df$Replicate, sep="_")
all.count.matrix <- matrix(0L, ncol=length(all.samps), nrow=length(vertex.list))
colnames(all.count.matrix) <- all.samps
for(x in seq_along(1:length(vertex.list))){
v.x <- vertex.list[[x]]
for(i in seq_along(1:length(all.samps))){
i.s <- all.samps[i]
i.s.vertices <- intersect(v.x, meta.df[meta.df$All.Sample == i.s, ]$Vertex)
all.count.matrix[x, i] <- length(i.s.vertices)
}
}
all.count.melt <- melt(all.count.matrix)
all.count.melt$Var2 <- as.character(all.count.melt$Var2)
all.count.melt$Block <- unlist(lapply(strsplit(all.count.melt$Var2, split="_", fixed=TRUE),
FUN=function(XP) paste0(XP[1])))
all.count.melt$Condition <- unlist(lapply(strsplit(all.count.melt$Var2, split="_", fixed=TRUE),
FUN=function(XP) paste0(XP[2])))
all.count.melt$Replicate <- unlist(lapply(strsplit(all.count.melt$Var2, split="_", fixed=TRUE),
FUN=function(XP) paste0(XP[3])))
ggplot(all.count.melt, aes(x=Block, y=value, fill=Condition)) +
geom_boxplot() +
theme_clean() +
facet_wrap(~Var1, scales="free_y")

Each panel is a neighbourhood, the numbers the counts of data points in each that com from either condition, and in the block of points. These are now concordant with the DA results, except neighbourhoods 7 and 26 - which look like a combination of sampling variance, which then creates a compositional effect. Either a filter on low-count neighbourhoods or on the log fold changes would ameliorate this.
NB: Emma suggested using the within neighbourhood distances instead of connectivity as a measure of density. This would require making our own KNN-graph building function that retains the distances between all pairs of vertices. In truth, this could just be added as the edge weights to the KNN-graph, which are ultimately ignored for the purpose of graph building, i.e. all edges = 1.
Using distance between vertices for spatial FDR correction.
We have the PC coordinates that were used in the original KNN graph building, so strictly we just need this with the neighbourhood list to calculate the Euclidean distances. For the neighbourhood, do we take the average off-diagonal distance as the density?
neighbour.dist.list <- list()
for(x in seq_along(1:length(vertex.list))){
x.verts <- vertex.list[[x]]
x.pcs <- sim1.pca$x[x.verts, c(1:30)]
x.euclid <- as.matrix(dist(x.pcs))
x.distdens <- 1/mean(x.euclid[lower.tri(x.euclid, diag=FALSE)])
neighbour.dist.list[[x]] <- x.distdens
}
hist(unlist(neighbour.dist.list), 100, xlab="1/mean Euclidean distance")

This looks a little like the 3 blocks of points… Let’s try this for weighting the spatial FDR.
graph_spatialFDR <- function(neighborhoods, graph, pvalues, connectivity='vertex', pca=NULL){
# input a set of neighborhoods as a list of graph vertices
# the input graph and the unadjusted GLM p-values
#' neighborhoods: list of vertices and their respective neighborhoods
#' graph: input kNN graph
#' pvalues: a vector of pvalues in the same order as the neighborhood indices
#' connectivity: character - edge or vertex to calculate neighborhood connectivity or distance to use average Euclidean distance
#' pca: matrix of PCs to calculate Euclidean distances, only required when connectivity == distance
# Discarding NA pvalues.
haspval <- !is.na(pvalues)
if (!all(haspval)) {
coords <- coords[haspval, , drop=FALSE]
pvalues <- pvalues[haspval]
}
# define the subgraph for each neighborhood then calculate the vertex connectivity for each
# this latter computation is quite slow - can it be sped up?
subgraphs <- lapply(1:length(neighborhoods[haspval]),
FUN=function(X) induced_subgraph(graph, neighborhoods[haspval][[X]]))
# now loop over these sub-graphs to calculate the connectivity - this seems a little slow...
if(connectivity == "vertex"){
t.connect <- lapply(subgraphs, FUN=function(EG) vertex_connectivity(EG))
} else if(connectivity == "edge"){
t.connect <- lapply(subgraphs, FUN=function(EG) edge_connectivity(EG))
} else if(connectivity == "distance"){
if(!is.null(pca)){
t.connect <- lapply(1:length(neighborhoods[haspval]),
FUN=function(PG) {
x.pcs <- pca[neighborhoods[haspval][[PG]], ]
x.euclid <- as.matrix(dist(x.pcs))
x.distdens <- 1/mean(x.euclid[lower.tri(x.euclid, diag=FALSE)])
return(x.distdens)})
} else{
errorCondition("A matrix of PCs is required to calculate distances")
}
}else{
errorCondition("connectivity option not recognised - must be either edge, vertex or distance")
}
# use 1/connectivity as the weighting for the weighted BH adjustment from Cydar
w <- 1/unlist(t.connect)
w[is.infinite(w)] <- 0
# Computing a density-weighted q-value.
o <- order(pvalues)
pvalues <- pvalues[o]
w <- w[o]
adjp <- numeric(length(o))
adjp[o] <- rev(cummin(rev(sum(w)*pvalues/cumsum(w))))
adjp <- pmin(adjp, 1)
if (!all(haspval)) {
refp <- rep(NA_real_, length(haspval))
refp[haspval] <- adjp
adjp <- refp
}
return(adjp)
}
start.time <- Sys.time()
sim1.spatialfdr <- graph_spatialFDR(neighborhoods=vertex.list, graph=sim1.knn, connectivity="distance",
pca=sim1.pca$x[, c(1:30)],
pvalues=sim1.res[order(sim1.res$Neighbourhood), ]$PValue)
end.time <- Sys.time()
distance.time <- end.time - start.time
sim1.res$SpatialFDR.Dist[order(sim1.res$Neighbourhood)] <- sim1.spatialfdr
qvals <- sim1.spatialfdr
is.sig <- qvals <= 0.01
summary(is.sig)
Mode FALSE TRUE
logical 62 120
How does this compare with using connectivity for the spatial FDR correction?
sink(file="/dev/null")
pdf("~/Dropbox/Milo/simulations/Connectivity_vs_distance_SpatialFDR.pdf",
heigh=3.15, width=4.25, useDingbats=FALSE)
plot(-log10(sim1.res$SpatialFDR.Dist), -log10(sim1.res$SpatialFDR), xlab="-log10 distance Spatial FDR",
ylab="-log10 connectivity Spatial FDR")
dev.off()
sink(file=NULL)
plot(-log10(sim1.res$SpatialFDR.Dist), -log10(sim1.res$SpatialFDR), xlab="-log10 distance Spatial FDR",
ylab="-log10 connectivity Spatial FDR")

Aside from the small number of false-positive samples, this works fairly well on these simulated data. Can we also get similar results with a real-world data? For this ’ll test the 1 and 52 week old TEC from our Ageing thymus paper, as these have the biggest differences between them.
Ageing TEC
# read in normalised data, subset to 1 and 52 week old TEC, do a PCA and construct a kNN graph.
tec.meta <- read.table("~/Dropbox/AgeingExperiment/Frozen/ThymusMarker_tSNE_PCA_meta.tsv",
sep="\t", header=TRUE, stringsAsFactors=FALSE)
# exclude technical artifact cluster
tec.meta <- tec.meta[!tec.meta$TFIDF.Cluster %in% c(4), ]
tec.sub.meta <- tec.meta[tec.meta$Age %in% c("1wk", "52wk"), ]
# add the label annotation
tec.sub.meta$Cluster <- "Unknown"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "2"] <- "Intertypical TEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "9"] <- "Perinatal cTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "3"] <- "Mature cTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "7"] <- "Mature mTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "1"] <- "Post-Aire mTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "5"] <- "Tuft-like mTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "6"] <- "Proliferating TEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "8"] <- "nTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "10"] <- "sTEC"
inter.cols <- c("#9970ab", "#35978f", "#B0cdc1", "#762a83", "#01665e", "#e7d4e8", "#dfc27d", "#8c510a" ,"#bf812d")
names(inter.cols) <- c("Post-Aire mTEC", 'Intertypical TEC', 'Mature cTEC', 'Tuft-like mTEC',
'Proliferating TEC', 'Mature mTEC', 'nTEC', 'Perinatal cTEC', 'sTEC')
tec.gex <- read.table("~/Dropbox/AgeingExperiment/Frozen/ThymusQC_SFnorm.tsv.gz",
sep="\t", header=TRUE, stringsAsFactors=FALSE)
tec.sub.gex <- tec.gex[, colnames(tec.gex) %in% tec.sub.meta$Sample]
tec.hvgs <- read.table("~/Dropbox/AgeingExperiment/Frozen/Thymus_HVG.tsv",
sep="\t", header=TRUE, stringsAsFactors=FALSE)
I’ll build a kNN-graph from the first 30 PCs previously computed on all TEC.
set.seed(42)
tec.knn <- buildKNNGraph(x=as.matrix(tec.sub.meta[, paste0("PC", 1:30)]), k=21, d=NA, transposed=TRUE)
tec.fr.layout <- layout_with_fr(tec.knn)
plot(tec.knn, layout=tec.fr.layout, vertex.frame.color='skyblue', vertex.color='skyblue', vertex.label.color='black',
vertex.label.family='Helvetica', edge.color='grey60', vertex.label.cex=0.9,
vertex.label.dist=1, edge.arrow.size=0.2)

This is a fairly densely connected network, how does the UMAP look?
set.seed(42)
tec.umap <- umap(as.matrix(tec.sub.meta[, paste0("PC", 1:30)]),
n_components=2,
n_neighbors=21, metric='euclidean',
init='random', min_dist=0.2)
tec.umap.df <- as.data.frame(tec.umap$layout)
colnames(tec.umap.df) <- c("UMAP1", "UMAP2")
tec.umap.df$Sample <- tec.sub.meta$Sample
tec.umap.merge <- merge(tec.umap.df, tec.sub.meta, by='Sample')
ggplot(tec.umap.merge, aes(x=UMAP1, y=UMAP2)) +
geom_point(aes(colour=Cluster)) +
theme_clean() +
scale_colour_manual(values=inter.cols) +
facet_wrap(~Age) +
guides(colour=guide_legend(override.aes=list(size=3)),
shape=guide_legend(override.aes=list(size=3)))

# randomly select vertices in the graph
n.hood <- 0.05
tec.random.vertices <- sample(V(tec.knn), size=floor(n.hood*length(V(tec.knn))))
# loop over random vertices and count the number of cells in each
tec.vertex.list <- sapply(1:length(tec.random.vertices), FUN=function(X) neighbors(tec.knn, v=tec.random.vertices[X]))
hist(unlist(lapply(tec.vertex.list, length)), 100, main="Histogram of neighbors", xlab="Neighbourhood size")

This is the histogram of TEC neighbourhood sizes.
tec.umap.merge$ExpSamp <- paste(tec.umap.merge$Age, tec.umap.merge$SortDay, sep="_")
tec.umap.merge$Vertex <- c(1:nrow(tec.umap.merge))
tec.counts <- quant_neighbourhood(graph=tec.knn, meta=tec.umap.merge, sample.column='ExpSamp', sample.vertices=n.hood)
tec.reps <- unlist(lapply(strsplit(unique(tec.umap.merge$ExpSamp), split="_"), FUN=function(X) paste0(X[2])))
tec.cond <- unlist(lapply(strsplit(unique(tec.umap.merge$ExpSamp), split="_"), FUN=function(X) paste0(X[1])))
tec.sample.meta <- data.frame("Condition"=tec.cond,
"Replicate"=tec.reps)
tec.sample.meta$Sample <- paste(tec.sample.meta$Condition, tec.sample.meta$Replicate, sep="_")
rownames(tec.sample.meta) <- tec.sample.meta$Sample
tec.model <- model.matrix(~ Condition, data=tec.sample.meta)
head(tec.model)
(Intercept) Condition52wk
1wk_4 1 0
52wk_5 1 1
1wk_5 1 0
1wk_2 1 0
1wk_1 1 0
1wk_3 1 0
tec.dge <- DGEList(tec.counts[, rownames(tec.model)], lib.size=log(colSums(tec.counts[, rownames(tec.model)])))
tec.dge <- estimateDisp(tec.dge, tec.model)
tec.fit <- glmQLFit(tec.dge, tec.model, robust=TRUE)
# tec.contrast <- makeContrasts(ConditionA - ConditionB, levels=tec.model)
# tec.res <- glmQLFTest(tec.fit, contrast=tec.contrast)
tec.res <- as.data.frame(topTags(glmQLFTest(tec.fit, coef=2), sort.by='none', n=Inf))
tec.res$Sig <- as.factor(as.numeric(tec.res$FDR <= 0.01))
tec.res$Neighbourhood <- as.numeric(rownames(tec.res))
# control the spatial FDR
qvals <- tec.res$PValue
is.sig <- qvals <= 0.01
summary(is.sig)
Mode FALSE TRUE
logical 27 18
There are 18 DA neighbourhoods - I expect these should reflect the Perinatal, Intertypical, Proliferating and sTEC. I’ll use the distance-based approach to correct for the spatial FDR.
tec.spatialfdr <- graph_spatialFDR(neighborhoods=tec.vertex.list, graph=tec.knn, connectivity="distance",
pca=as.matrix(tec.sub.meta[, paste0("PC", 1:30)]),
pvalues=tec.res[order(tec.res$Neighbourhood), ]$PValue)
tec.res$SpatialFDR[order(tec.res$Neighbourhood)] <- tec.spatialfdr
qvals <- tec.spatialfdr
is.sig <- qvals <= 0.01
summary(is.sig)
Mode FALSE TRUE
logical 30 15
Interesting, 3 neighbourhoods are no longer statistically significant after the spatial FDR correction - hopefully these are genuinely false-positives.
In each neighbourhood, what is the most common condition or block of cells?
tec.neighbour.exprs <- neighborhood_expression(tec.vertex.list, tec.sub.gex)
Embed these hyperspheres with a PCA and UMAP.
tec.neighbour.pca <- prcomp((t(tec.neighbour.exprs[tec.hvgs$HVG, ])))
pairs(tec.neighbour.pca$x[, c(1:5)])

set.seed(42)
neighbourhood.umap <- umap(tec.neighbour.pca$x[, c(1:30)],
n_components=2,
n_neighbors=21, metric='euclidean',
init='random', min_dist=0.1)
We can overlay the DA testing on these neighbourhoods.
tec.neighbor.df <- tec.res[, c("logFC", "Neighbourhood", "SpatialFDR")]
tec.neighbor.df <- do.call(cbind.data.frame, list(tec.neighbor.df, as.data.frame(neighbourhood.umap$layout)))
colnames(tec.neighbor.df) <- c("logFC", "Neighbourhood", "SpatialFDR", "UMAP1", "UMAP2")
tec.neighbor.df$Sig <- as.numeric(tec.neighbor.df$SpatialFDR <= 0.05)
ggplot(tec.neighbor.df, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=tec.neighbor.df[tec.neighbor.df$Sig == 0, ],
colour='grey80', size=2) +
geom_point(data=tec.neighbor.df[tec.neighbor.df$Sig == 1, ],
aes(colour=logFC), size=4) +
theme_clean() +
scale_colour_gradient2(low="blue", mid="grey80", high="red")

Some are up and some are down. Which TEC subtypes do they largely correspond with?
tec.neighbour.list <- list()
for(x in seq_along(1:length(tec.vertex.list))){
x.df <- tec.umap.merge[tec.umap.merge$Vertex %in% tec.vertex.list[[x]], ]
x.rep <- names(table(x.df$SortDay))[which(table(x.df$SortDay) == max(table(x.df$SortDay)))]
if(length(x.rep) > 1){
x.rep <- sample(size=1, x.rep)
}
x.block <- names(table(x.df$Cluster))[which(table(x.df$Cluster) == max(table(x.df$Cluster)))]
if(length(x.block) > 1){
x.block <- sample(size=1, x.block)
}
x.condition <- names(table(x.df$Age))[which(table(x.df$Age) == max(table(x.df$Age)))]
if(length(x.condition) > 1){
x.condition <- sample(size=1, x.condition)
}
tec.neighbour.list[[x]] <- data.frame("Replicate"=x.rep, "Cluster"=x.block, "Condition"=x.condition, "Neighbourhood"=x)
}
tec.neighbour.meta <- do.call(rbind.data.frame, tec.neighbour.list)
tec.neighbour.merge <- merge(tec.neighbor.df, tec.neighbour.meta, by='Neighbourhood')
tec.neighbour.merge$Diff <- sign(tec.neighbour.merge$logFC)
tec.neighbour.merge$Diff[tec.neighbour.merge$Sig == 0] <- 0
ggplot(tec.neighbour.merge, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=tec.neighbour.merge[, c("UMAP1", "UMAP2")],
colour='grey80', size=1) +
geom_point(data=tec.neighbour.merge[tec.neighbour.merge$Sig == 1, ],
aes(colour=logFC), size=4) +
theme_clean() +
scale_colour_gradient2(low="blue", mid="grey80", high="red") +
facet_wrap(~Cluster)

There is a subset of the Proliferating TEC that are down, good, as are the Perinatal cTEC. Likewise, most of the Intertypical TEC are up as well, but some are also down. I don’t know if this is because of a compositional effect or because these are the ones that would be differentiating into mTEC via the Proliferating TEC compartment.
table(tec.neighbour.merge$Cluster, tec.neighbour.merge$Diff)
-1 0 1
Mature mTEC 0 16 0
Intertypical TEC 2 6 10
Perinatal cTEC 4 0 0
Mature cTEC 0 1 3
Post-Aire mTEC 0 2 0
Proliferating TEC 1 0 0
I would say that these results make a lot of sense. Can I now extend this to include all time points and fit age as a linear ordinal variable?
Extended TEC DA testing.
# exclude technical artifact cluster
tec.meta <- tec.meta[!tec.meta$TFIDF.Cluster %in% c(4), ]
tec.sub.meta <- tec.meta
tec.sub.meta$AgeFactor <- ordered(tec.sub.meta$Age,
levels=c("1wk", "4wk", "16wk", "32wk", "52wk"))
# add the label annotation
tec.sub.meta$Cluster <- "Unknown"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "2"] <- "Intertypical TEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "9"] <- "Perinatal cTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "3"] <- "Mature cTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "7"] <- "Mature mTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "1"] <- "Post-Aire mTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "5"] <- "Tuft-like mTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "6"] <- "Proliferating TEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "8"] <- "nTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "10"] <- "sTEC"
inter.cols <- c("#9970ab", "#35978f", "#B0cdc1", "#762a83", "#01665e", "#e7d4e8", "#dfc27d", "#8c510a" ,"#bf812d")
names(inter.cols) <- c("Post-Aire mTEC", 'Intertypical TEC', 'Mature cTEC', 'Tuft-like mTEC',
'Proliferating TEC', 'Mature mTEC', 'nTEC', 'Perinatal cTEC', 'sTEC')
tec.sub.gex <- tec.gex[, colnames(tec.gex) %in% tec.sub.meta$Sample]
I’ll build a kNN-graph from the first 30 PCs previously computed on all TEC.
set.seed(42)
tec.knn <- buildKNNGraph(x=as.matrix(tec.sub.meta[, paste0("PC", 1:30)]), k=21, d=NA, transposed=TRUE)
tec.fr.layout <- layout_with_fr(tec.knn)
plot(tec.knn, layout=tec.fr.layout, vertex.frame.color='skyblue', vertex.color='skyblue', vertex.label.color='black',
vertex.label.family='Helvetica', edge.color='grey60', vertex.label.cex=0.9,
vertex.label.dist=1, edge.arrow.size=0.2)

This is a fairly densely connected network, how does the UMAP look?
set.seed(42)
tec.umap <- umap(as.matrix(tec.sub.meta[, paste0("PC", 1:30)]),
n_components=2,
n_neighbors=21, metric='euclidean',
init='random', min_dist=0.2)
tec.umap.df <- as.data.frame(tec.umap$layout)
colnames(tec.umap.df) <- c("UMAP1", "UMAP2")
tec.umap.df$Sample <- tec.sub.meta$Sample
tec.umap.merge <- merge(tec.umap.df, tec.sub.meta, by='Sample')
ggplot(tec.umap.merge, aes(x=UMAP1, y=UMAP2)) +
geom_point(aes(colour=Cluster)) +
theme_clean() +
scale_colour_manual(values=inter.cols) +
facet_wrap(~AgeFactor) +
guides(colour=guide_legend(override.aes=list(size=3)),
shape=guide_legend(override.aes=list(size=3)))

# randomly select vertices in the graph
n.hood <- 0.05
tec.random.vertices <- sample(V(tec.knn), size=floor(n.hood*length(V(tec.knn))))
# loop over random vertices and count the number of cells in each
tec.vertex.list <- sapply(1:length(tec.random.vertices), FUN=function(X) neighbors(tec.knn, v=tec.random.vertices[X]))
hist(unlist(lapply(tec.vertex.list, length)), 100, main="Histogram of neighbors", xlab="Neighbourhood size")

This is the histogram of TEC neighbourhood sizes.
tec.umap.merge$ExpSamp <- paste(tec.umap.merge$Age, tec.umap.merge$SortDay, sep="_")
tec.umap.merge$Vertex <- c(1:nrow(tec.umap.merge))
tec.counts <- quant_neighbourhood(graph=tec.knn, meta=tec.umap.merge, sample.column='ExpSamp', sample.vertices=n.hood)
tec.reps <- unlist(lapply(strsplit(unique(tec.umap.merge$ExpSamp), split="_"), FUN=function(X) paste0(X[2])))
tec.cond <- unlist(lapply(strsplit(unique(tec.umap.merge$ExpSamp), split="_"), FUN=function(X) paste0(X[1])))
tec.sample.meta <- data.frame("Condition"=tec.cond,
"Replicate"=tec.reps)
tec.sample.meta$Sample <- paste(tec.sample.meta$Condition, tec.sample.meta$Replicate, sep="_")
rownames(tec.sample.meta) <- tec.sample.meta$Sample
tec.sample.meta$Condition <- ordered(tec.sample.meta$Condition,
levels=c("1wk", "4wk", "16wk", "32wk", "52wk"))
tec.model <- model.matrix(~ Condition, data=tec.sample.meta)
head(tec.model)
(Intercept) Condition.L Condition.Q Condition.C Condition^4
1wk_4 1 -0.6324555 0.5345225 -0.3162278 0.1195229
4wk_3 1 -0.3162278 -0.2672612 0.6324555 -0.4780914
32wk_2 1 0.3162278 -0.2672612 -0.6324555 -0.4780914
32wk_5 1 0.3162278 -0.2672612 -0.6324555 -0.4780914
52wk_5 1 0.6324555 0.5345225 0.3162278 0.1195229
4wk_2 1 -0.3162278 -0.2672612 0.6324555 -0.4780914
tec.dge <- DGEList(tec.counts[, rownames(tec.model)], lib.size=log(colSums(tec.counts[, rownames(tec.model)])))
tec.dge <- estimateDisp(tec.dge, tec.model)
tec.fit <- glmQLFit(tec.dge, tec.model, robust=TRUE)
# tec.contrast <- makeContrasts(ConditionA - ConditionB, levels=tec.model)
# tec.res <- glmQLFTest(tec.fit, contrast=tec.contrast)
tec.res <- as.data.frame(topTags(glmQLFTest(tec.fit, coef=2), sort.by='none', n=Inf))
tec.res$Sig <- as.factor(as.numeric(tec.res$FDR <= 0.01))
tec.res$Neighbourhood <- as.numeric(rownames(tec.res))
# control the spatial FDR
qvals <- tec.res$PValue
is.sig <- qvals <= 0.01
summary(is.sig)
Mode FALSE TRUE
logical 70 46
There are 46 DA neighbourhoods - I expect these should reflect the Perinatal, Intertypical, Proliferating and sTEC. I’ll use the distance-based approach to correct for the spatial FDR.
tec.spatialfdr <- graph_spatialFDR(neighborhoods=tec.vertex.list, graph=tec.knn, connectivity="distance",
pca=as.matrix(tec.sub.meta[, paste0("PC", 1:30)]),
pvalues=tec.res[order(tec.res$Neighbourhood), ]$PValue)
tec.res$SpatialFDR[order(tec.res$Neighbourhood)] <- tec.spatialfdr
qvals <- tec.spatialfdr
is.sig <- qvals <= 0.01
summary(is.sig)
Mode FALSE TRUE
logical 82 34
Interesting, 12 neighbourhoods are no longer statistically significant after the spatial FDR correction - hopefully these are genuinely false-positives.
In each neighbourhood, what is the most common condition or block of cells?
tec.neighbour.exprs <- neighborhood_expression(tec.vertex.list, tec.sub.gex)
Embed these hyperspheres with a PCA and UMAP.
tec.neighbour.pca <- prcomp((t(tec.neighbour.exprs[tec.hvgs$HVG, ])))
pairs(tec.neighbour.pca$x[, c(1:5)])

set.seed(42)
neighbourhood.umap <- umap(tec.neighbour.pca$x[, c(1:30)],
n_components=2,
n_neighbors=21, metric='euclidean',
init='random', min_dist=0.1)
We can overlay the DA testing on these neighbourhoods.
tec.neighbor.df <- tec.res[, c("logFC", "Neighbourhood", "SpatialFDR")]
tec.neighbor.df <- do.call(cbind.data.frame, list(tec.neighbor.df, as.data.frame(neighbourhood.umap$layout)))
colnames(tec.neighbor.df) <- c("logFC", "Neighbourhood", "SpatialFDR", "UMAP1", "UMAP2")
tec.neighbor.df$Sig <- as.numeric(tec.neighbor.df$SpatialFDR <= 0.05)
ggplot(tec.neighbor.df, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=tec.neighbor.df[tec.neighbor.df$Sig == 0, ],
colour='grey80', size=2) +
geom_point(data=tec.neighbor.df[tec.neighbor.df$Sig == 1, ],
aes(colour=logFC), size=4) +
theme_clean() +
scale_colour_gradient2(low="blue", mid="grey80", high="red")

Some are up and some are down. Which TEC subtypes do they largely correspond with? That big streak of lower abundance neighbourhoods should be the differentiation trajectory from Intertypical to Mature mTEC.
tec.neighbour.list <- list()
for(x in seq_along(1:length(tec.vertex.list))){
x.df <- tec.umap.merge[tec.umap.merge$Vertex %in% tec.vertex.list[[x]], ]
x.rep <- names(table(x.df$SortDay))[which(table(x.df$SortDay) == max(table(x.df$SortDay)))]
if(length(x.rep) > 1){
x.rep <- sample(size=1, x.rep)
}
x.block <- names(table(x.df$Cluster))[which(table(x.df$Cluster) == max(table(x.df$Cluster)))]
if(length(x.block) > 1){
x.block <- sample(size=1, x.block)
}
x.condition <- names(table(x.df$Age))[which(table(x.df$Age) == max(table(x.df$Age)))]
if(length(x.condition) > 1){
x.condition <- sample(size=1, x.condition)
}
tec.neighbour.list[[x]] <- data.frame("Replicate"=x.rep, "Cluster"=x.block, "Condition"=x.condition, "Neighbourhood"=x)
}
tec.neighbour.meta <- do.call(rbind.data.frame, tec.neighbour.list)
tec.neighbour.merge <- merge(tec.neighbor.df, tec.neighbour.meta, by='Neighbourhood')
tec.neighbour.merge$Diff <- sign(tec.neighbour.merge$logFC)
tec.neighbour.merge$Diff[tec.neighbour.merge$Sig == 0] <- 0
ggplot(tec.neighbour.merge, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=tec.neighbour.merge[, c("UMAP1", "UMAP2")],
colour='grey80', size=1) +
geom_point(data=tec.neighbour.merge[tec.neighbour.merge$Sig == 1, ],
aes(colour=logFC), size=4) +
theme_clean() +
scale_colour_gradient2(low="blue", mid="grey80", high="red") +
facet_wrap(~Cluster)

This very nicely recapitulates the DA testing using clusters, and it pinpoints the loss of medulla-biased Intertypical TEC which we only really observed in our larger experiments. This is working beyond my wildest dreams!!
table(tec.neighbour.merge$Cluster, tec.neighbour.merge$Diff)
-1 0 1
Mature mTEC 4 32 2
Proliferating TEC 7 4 0
Intertypical TEC 8 12 18
Perinatal cTEC 5 0 0
sTEC 0 0 1
Post-Aire mTEC 0 7 0
Mature cTEC 0 6 5
nTEC 0 2 0
Tuft-like mTEC 0 3 0
I would say that these results make a lot of sense. I’ll extend it to include the quadratic testing which should pick up the inverse-parabolic profile of the Post-Aire mTEC population.
quad.tec.res <- as.data.frame(topTags(glmQLFTest(tec.fit, coef=3), sort.by='none', n=Inf))
quad.tec.res$Sig <- as.factor(as.numeric(quad.tec.res$FDR <= 0.01))
quad.tec.res$Neighbourhood <- as.numeric(rownames(quad.tec.res))
# control the spatial FDR
qvals <- quad.tec.res$PValue
is.sig <- qvals <= 0.01
summary(is.sig)
Mode FALSE TRUE
logical 110 6
There are 6 DA neighbourhoods from the quadratic model - I expect these should reflect the Post-Aire mTEC.
quad.tec.spatialfdr <- graph_spatialFDR(neighborhoods=tec.vertex.list, graph=tec.knn, connectivity="distance",
pca=as.matrix(tec.sub.meta[, paste0("PC", 1:30)]),
pvalues=quad.tec.res[order(quad.tec.res$Neighbourhood), ]$PValue)
quad.tec.res$SpatialFDR[order(quad.tec.res$Neighbourhood)] <- quad.tec.spatialfdr
qvals <- quad.tec.spatialfdr
is.sig <- qvals <= 0.05
summary(is.sig)
Mode FALSE
logical 116
Dang, clearly the small group of Post-Aire mTEC means this isn’t sufficiently sensitive after multiple-testing correction.
ggplot(tec.neighbour.merge, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=tec.neighbour.merge[, c("UMAP1", "UMAP2")],
colour='grey80', size=1) +
geom_point(data=tec.neighbour.merge,
aes(colour=Cluster), size=4) +
theme_clean() +
scale_colour_manual(values=inter.cols) +
facet_wrap(~Cluster)

Hmm, the Post-Aire mTEC don’t form a single neighbourhood on their own, nor do the nTEC or Tuft-like mTEC. There is definitely a limit to the resolution. Would a smaller \(k\) resolve this?
Ageing TEC with k=11
tec.sub.meta <- tec.meta[tec.meta$Age %in% c("1wk", "52wk"), ]
# add the label annotation
tec.sub.meta$Cluster <- "Unknown"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "2"] <- "Intertypical TEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "9"] <- "Perinatal cTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "3"] <- "Mature cTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "7"] <- "Mature mTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "1"] <- "Post-Aire mTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "5"] <- "Tuft-like mTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "6"] <- "Proliferating TEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "8"] <- "nTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "10"] <- "sTEC"
inter.cols <- c("#9970ab", "#35978f", "#B0cdc1", "#762a83", "#01665e", "#e7d4e8", "#dfc27d", "#8c510a" ,"#bf812d")
names(inter.cols) <- c("Post-Aire mTEC", 'Intertypical TEC', 'Mature cTEC', 'Tuft-like mTEC',
'Proliferating TEC', 'Mature mTEC', 'nTEC', 'Perinatal cTEC', 'sTEC')
I’ll build a kNN-graph from the first 30 PCs previously computed on all TEC, but k=11 this time.
set.seed(42)
tec.knn <- buildKNNGraph(x=as.matrix(tec.sub.meta[, paste0("PC", 1:30)]), k=11, d=NA, transposed=TRUE)
tec.fr.layout <- layout_with_fr(tec.knn)
plot(tec.knn, layout=tec.fr.layout, vertex.frame.color='skyblue', vertex.color='skyblue', vertex.label.color='black',
vertex.label.family='Helvetica', edge.color='grey60', vertex.label.cex=0.9,
vertex.label.dist=1, edge.arrow.size=0.2)

This is a fairly densely connected network still, even with k=11, how does the UMAP look?
set.seed(42)
tec.umap <- umap(as.matrix(tec.sub.meta[, paste0("PC", 1:30)]),
n_components=2,
n_neighbors=11, metric='euclidean',
init='random', min_dist=0.2)
tec.umap.df <- as.data.frame(tec.umap$layout)
colnames(tec.umap.df) <- c("UMAP1", "UMAP2")
tec.umap.df$Sample <- tec.sub.meta$Sample
tec.umap.merge <- merge(tec.umap.df, tec.sub.meta, by='Sample')
ggplot(tec.umap.merge, aes(x=UMAP1, y=UMAP2)) +
geom_point(aes(colour=Cluster)) +
theme_clean() +
scale_colour_manual(values=inter.cols) +
facet_wrap(~Age) +
guides(colour=guide_legend(override.aes=list(size=3)),
shape=guide_legend(override.aes=list(size=3)))

For a smaller k, does there need to be a higher density of sampling, i.e. more neighbourhoods? I’ll set it to 10% here instead.
# randomly select vertices in the graph
n.hood <- 0.10
tec.random.vertices <- sample(V(tec.knn), size=floor(n.hood*length(V(tec.knn))))
# loop over random vertices and count the number of cells in each
tec.vertex.list <- sapply(1:length(tec.random.vertices), FUN=function(X) neighbors(tec.knn, v=tec.random.vertices[X]))
hist(unlist(lapply(tec.vertex.list, length)), 100, main="Histogram of neighbors", xlab="Neighbourhood size")

This is the histogram of TEC neighbourhood sizes.
tec.umap.merge$ExpSamp <- paste(tec.umap.merge$Age, tec.umap.merge$SortDay, sep="_")
tec.umap.merge$Vertex <- c(1:nrow(tec.umap.merge))
tec.counts <- quant_neighbourhood(graph=tec.knn, meta=tec.umap.merge, sample.column='ExpSamp', sample.vertices=n.hood)
tec.reps <- unlist(lapply(strsplit(unique(tec.umap.merge$ExpSamp), split="_"), FUN=function(X) paste0(X[2])))
tec.cond <- unlist(lapply(strsplit(unique(tec.umap.merge$ExpSamp), split="_"), FUN=function(X) paste0(X[1])))
tec.sample.meta <- data.frame("Condition"=tec.cond,
"Replicate"=tec.reps)
tec.sample.meta$Sample <- paste(tec.sample.meta$Condition, tec.sample.meta$Replicate, sep="_")
rownames(tec.sample.meta) <- tec.sample.meta$Sample
tec.model <- model.matrix(~ Condition, data=tec.sample.meta)
head(tec.model)
(Intercept) Condition52wk
1wk_4 1 0
52wk_5 1 1
1wk_5 1 0
1wk_2 1 0
1wk_1 1 0
1wk_3 1 0
tec.dge <- DGEList(tec.counts[, rownames(tec.model)], lib.size=log(colSums(tec.counts[, rownames(tec.model)])))
tec.dge <- estimateDisp(tec.dge, tec.model)
tec.fit <- glmQLFit(tec.dge, tec.model, robust=TRUE)
# tec.contrast <- makeContrasts(ConditionA - ConditionB, levels=tec.model)
# tec.res <- glmQLFTest(tec.fit, contrast=tec.contrast)
tec.res <- as.data.frame(topTags(glmQLFTest(tec.fit, coef=2), sort.by='none', n=Inf))
tec.res$Sig <- as.factor(as.numeric(tec.res$FDR <= 0.05))
tec.res$Neighbourhood <- as.numeric(rownames(tec.res))
# control the spatial FDR
qvals <- tec.res$PValue
is.sig <- qvals <= 0.05
summary(is.sig)
Mode FALSE TRUE
logical 43 48
I have increased the total number of neighbourhoods defined with k=11, there will almost certainly be a trade-off between sensitivity and power w.r.t. the extra multiple-testing burden, as well as the higher sampling variance as each neighbourhood will contain fewer cells <- this will be something we need to optimise in some way.
tec.spatialfdr <- graph_spatialFDR(neighborhoods=tec.vertex.list, graph=tec.knn, connectivity="distance",
pca=as.matrix(tec.sub.meta[, paste0("PC", 1:30)]),
pvalues=tec.res[order(tec.res$Neighbourhood), ]$PValue)
tec.res$SpatialFDR[order(tec.res$Neighbourhood)] <- tec.spatialfdr
qvals <- tec.spatialfdr
is.sig <- qvals <= 0.05
summary(is.sig)
Mode FALSE TRUE
logical 54 37
Interesting, 11 of the neighbourhoods are no long statistically significant after the spatial FDR correction. Clearly there is a trade-off between neighbourhood size, sensitivity and power.
In each neighbourhood, what is the most common condition or block of cells?
tec.neighbour.exprs <- neighborhood_expression(tec.vertex.list, tec.sub.gex)
Embed these hyperspheres with a PCA and UMAP.
tec.neighbour.pca <- prcomp((t(tec.neighbour.exprs[tec.hvgs$HVG, ])))
pairs(tec.neighbour.pca$x[, c(1:5)])

set.seed(42)
neighbourhood.umap <- umap(tec.neighbour.pca$x[, c(1:30)],
n_components=2,
n_neighbors=11, metric='euclidean',
init='random', min_dist=0.1)
We can overlay the DA testing on these neighbourhoods.
tec.neighbor.df <- tec.res[, c("logFC", "Neighbourhood", "SpatialFDR")]
tec.neighbor.df <- do.call(cbind.data.frame, list(tec.neighbor.df, as.data.frame(neighbourhood.umap$layout)))
colnames(tec.neighbor.df) <- c("logFC", "Neighbourhood", "SpatialFDR", "UMAP1", "UMAP2")
tec.neighbor.df$Sig <- as.numeric(tec.neighbor.df$SpatialFDR <= 0.05)
ggplot(tec.neighbor.df, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=tec.neighbor.df[tec.neighbor.df$Sig == 0, ],
colour='grey80', size=2) +
geom_point(data=tec.neighbor.df[tec.neighbor.df$Sig == 1, ],
aes(colour=logFC), size=4) +
theme_clean() +
scale_colour_gradient2(low="blue", mid="grey80", high="red")

Some are up and some are down. Which TEC subtypes do they largely correspond with?
tec.neighbour.list <- list()
for(x in seq_along(1:length(tec.vertex.list))){
x.df <- tec.umap.merge[tec.umap.merge$Vertex %in% tec.vertex.list[[x]], ]
x.rep <- names(table(x.df$SortDay))[which(table(x.df$SortDay) == max(table(x.df$SortDay)))]
if(length(x.rep) > 1){
x.rep <- sample(size=1, x.rep)
}
x.block <- names(table(x.df$Cluster))[which(table(x.df$Cluster) == max(table(x.df$Cluster)))]
if(length(x.block) > 1){
x.block <- sample(size=1, x.block)
}
x.condition <- names(table(x.df$Age))[which(table(x.df$Age) == max(table(x.df$Age)))]
if(length(x.condition) > 1){
x.condition <- sample(size=1, x.condition)
}
tec.neighbour.list[[x]] <- data.frame("Replicate"=x.rep, "Cluster"=x.block, "Condition"=x.condition, "Neighbourhood"=x)
}
tec.neighbour.meta <- do.call(rbind.data.frame, tec.neighbour.list)
tec.neighbour.merge <- merge(tec.neighbor.df, tec.neighbour.meta, by='Neighbourhood')
tec.neighbour.merge$Diff <- sign(tec.neighbour.merge$logFC)
tec.neighbour.merge$Diff[tec.neighbour.merge$Sig == 0] <- 0
ggplot(tec.neighbour.merge, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=tec.neighbour.merge[, c("UMAP1", "UMAP2")],
colour='grey80', size=1) +
geom_point(data=tec.neighbour.merge[tec.neighbour.merge$Sig == 1, ],
aes(colour=logFC), size=4) +
theme_clean() +
scale_colour_gradient2(low="blue", mid="grey80", high="red") +
facet_wrap(~Cluster)

There is a subset of the Proliferating TEC that are down, good, as are the Perinatal cTEC. Likewise, most of the Intertypical TEC are up as well, but some are also down. I don’t know if this is because of a compositional effect or because these are the ones that would be differentiating into mTEC via the Proliferating TEC compartment.
table(tec.neighbour.merge$Cluster, tec.neighbour.merge$Diff)
-1 0 1
Mature mTEC 0 30 0
Intertypical TEC 4 14 17
Perinatal cTEC 8 1 0
Mature cTEC 1 1 6
Post-Aire mTEC 0 4 0
Proliferating TEC 1 3 0
Tuft-like mTEC 0 1 0
I would say that these results make a lot of sense. Can I now extend this to include all time points and fit age as a linear ordinal variable?
Extended TEC DA testing with k=11
# exclude technical artifact cluster
tec.sub.meta <- tec.meta
tec.sub.meta$AgeFactor <- ordered(tec.sub.meta$Age,
levels=c("1wk", "4wk", "16wk", "32wk", "52wk"))
# add the label annotation
tec.sub.meta$Cluster <- "Unknown"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "2"] <- "Intertypical TEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "9"] <- "Perinatal cTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "3"] <- "Mature cTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "7"] <- "Mature mTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "1"] <- "Post-Aire mTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "5"] <- "Tuft-like mTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "6"] <- "Proliferating TEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "8"] <- "nTEC"
tec.sub.meta$Cluster[tec.sub.meta$TFIDF.Cluster == "10"] <- "sTEC"
inter.cols <- c("#9970ab", "#35978f", "#B0cdc1", "#762a83", "#01665e", "#e7d4e8", "#dfc27d", "#8c510a" ,"#bf812d")
names(inter.cols) <- c("Post-Aire mTEC", 'Intertypical TEC', 'Mature cTEC', 'Tuft-like mTEC',
'Proliferating TEC', 'Mature mTEC', 'nTEC', 'Perinatal cTEC', 'sTEC')
tec.sub.gex <- tec.gex[, colnames(tec.gex) %in% tec.sub.meta$Sample]
I’ll build a kNN-graph from the first 30 PCs previously computed on all TEC.
set.seed(42)
tec.knn <- buildKNNGraph(x=as.matrix(tec.sub.meta[, paste0("PC", 1:30)]), k=11, d=NA, transposed=TRUE)
tec.fr.layout <- layout_with_fr(tec.knn)
plot(tec.knn, layout=tec.fr.layout, vertex.frame.color='skyblue', vertex.color='skyblue', vertex.label.color='black',
vertex.label.family='Helvetica', edge.color='grey60', vertex.label.cex=0.9,
vertex.label.dist=1, edge.arrow.size=0.2)

This is a fairly densely connected network, how does the UMAP look?
set.seed(42)
tec.umap <- umap(as.matrix(tec.sub.meta[, paste0("PC", 1:30)]),
n_components=2,
n_neighbors=11, metric='euclidean',
init='random', min_dist=0.2)
tec.umap.df <- as.data.frame(tec.umap$layout)
colnames(tec.umap.df) <- c("UMAP1", "UMAP2")
tec.umap.df$Sample <- tec.sub.meta$Sample
tec.umap.merge <- merge(tec.umap.df, tec.sub.meta, by='Sample')
ggplot(tec.umap.merge, aes(x=UMAP1, y=UMAP2)) +
geom_point(aes(colour=Cluster)) +
theme_clean() +
scale_colour_manual(values=inter.cols) +
facet_wrap(~AgeFactor) +
guides(colour=guide_legend(override.aes=list(size=3)),
shape=guide_legend(override.aes=list(size=3)))

# randomly select vertices in the graph
n.hood <- 0.10
tec.random.vertices <- sample(V(tec.knn), size=floor(n.hood*length(V(tec.knn))))
# loop over random vertices and count the number of cells in each
tec.vertex.list <- sapply(1:length(tec.random.vertices), FUN=function(X) neighbors(tec.knn, v=tec.random.vertices[X]))
hist(unlist(lapply(tec.vertex.list, length)), 100, main="Histogram of neighbors", xlab="Neighbourhood size")

This is the histogram of TEC neighbourhood sizes.
tec.umap.merge$ExpSamp <- paste(tec.umap.merge$Age, tec.umap.merge$SortDay, sep="_")
tec.umap.merge$Vertex <- c(1:nrow(tec.umap.merge))
tec.counts <- quant_neighbourhood(graph=tec.knn, meta=tec.umap.merge, sample.column='ExpSamp', sample.vertices=n.hood)
tec.reps <- unlist(lapply(strsplit(unique(tec.umap.merge$ExpSamp), split="_"), FUN=function(X) paste0(X[2])))
tec.cond <- unlist(lapply(strsplit(unique(tec.umap.merge$ExpSamp), split="_"), FUN=function(X) paste0(X[1])))
tec.sample.meta <- data.frame("Condition"=tec.cond,
"Replicate"=tec.reps)
tec.sample.meta$Sample <- paste(tec.sample.meta$Condition, tec.sample.meta$Replicate, sep="_")
rownames(tec.sample.meta) <- tec.sample.meta$Sample
tec.sample.meta$Condition <- ordered(tec.sample.meta$Condition,
levels=c("1wk", "4wk", "16wk", "32wk", "52wk"))
tec.model <- model.matrix(~ Condition, data=tec.sample.meta)
head(tec.model)
(Intercept) Condition.L Condition.Q Condition.C Condition^4
1wk_4 1 -0.6324555 0.5345225 -0.3162278 0.1195229
4wk_3 1 -0.3162278 -0.2672612 0.6324555 -0.4780914
32wk_2 1 0.3162278 -0.2672612 -0.6324555 -0.4780914
32wk_5 1 0.3162278 -0.2672612 -0.6324555 -0.4780914
52wk_5 1 0.6324555 0.5345225 0.3162278 0.1195229
4wk_2 1 -0.3162278 -0.2672612 0.6324555 -0.4780914
tec.dge <- DGEList(tec.counts[, rownames(tec.model)], lib.size=log(colSums(tec.counts[, rownames(tec.model)])))
tec.dge <- estimateDisp(tec.dge, tec.model)
tec.fit <- glmQLFit(tec.dge, tec.model, robust=TRUE)
# tec.contrast <- makeContrasts(ConditionA - ConditionB, levels=tec.model)
# tec.res <- glmQLFTest(tec.fit, contrast=tec.contrast)
tec.res <- as.data.frame(topTags(glmQLFTest(tec.fit, coef=2), sort.by='none', n=Inf))
tec.res$Sig <- as.factor(as.numeric(tec.res$FDR <= 0.05))
tec.res$Neighbourhood <- as.numeric(rownames(tec.res))
# control the spatial FDR
qvals <- tec.res$PValue
is.sig <- qvals <= 0.05
summary(is.sig)
Mode FALSE TRUE
logical 110 122
There are 85 DA neighbourhoods - I expect these should reflect the Perinatal, Intertypical, Proliferating and sTEC. I’ll use the distance-based approach to correct for the spatial FDR.
tec.spatialfdr <- graph_spatialFDR(neighborhoods=tec.vertex.list, graph=tec.knn, connectivity="distance",
pca=as.matrix(tec.sub.meta[, paste0("PC", 1:30)]),
pvalues=tec.res[order(tec.res$Neighbourhood), ]$PValue)
tec.res$SpatialFDR[order(tec.res$Neighbourhood)] <- tec.spatialfdr
qvals <- tec.spatialfdr
is.sig <- qvals <= 0.05
summary(is.sig)
Mode FALSE TRUE
logical 136 96
Interesting, there are 26 neighbourhoods no longer statistically significant after the spatial FDR correction - hopefully these are genuinely false-positives.
In each neighbourhood, what is the most common condition or block of cells?
tec.neighbour.exprs <- neighborhood_expression(tec.vertex.list, tec.sub.gex)
Embed these hyperspheres with a PCA and UMAP.
tec.neighbour.pca <- prcomp((t(tec.neighbour.exprs[tec.hvgs$HVG, ])))
pairs(tec.neighbour.pca$x[, c(1:5)])

set.seed(42)
neighbourhood.umap <- umap(tec.neighbour.pca$x[, c(1:30)],
n_components=2,
n_neighbors=11, metric='euclidean',
init='random', min_dist=0.1)
We can overlay the DA testing on these neighbourhoods.
tec.neighbor.df <- tec.res[, c("logFC", "Neighbourhood", "SpatialFDR")]
tec.neighbor.df <- do.call(cbind.data.frame, list(tec.neighbor.df, as.data.frame(neighbourhood.umap$layout)))
colnames(tec.neighbor.df) <- c("logFC", "Neighbourhood", "SpatialFDR", "UMAP1", "UMAP2")
tec.neighbor.df$Sig <- as.numeric(tec.neighbor.df$SpatialFDR <= 0.05)
ggplot(tec.neighbor.df, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=tec.neighbor.df[tec.neighbor.df$Sig == 0, ],
colour='grey80', size=2) +
geom_point(data=tec.neighbor.df[tec.neighbor.df$Sig == 1, ],
aes(colour=logFC), size=4) +
theme_clean() +
scale_colour_gradient2(low="blue", mid="grey80", high="red")

Some are up and some are down. Which TEC subtypes do they largely correspond with? That big streak of lower abundance neighbourhoods should be the differentiation trajectory from Intertypical to Mature mTEC.
tec.neighbour.list <- list()
for(x in seq_along(1:length(tec.vertex.list))){
x.df <- tec.umap.merge[tec.umap.merge$Vertex %in% tec.vertex.list[[x]], ]
x.rep <- names(table(x.df$SortDay))[which(table(x.df$SortDay) == max(table(x.df$SortDay)))]
if(length(x.rep) > 1){
x.rep <- sample(size=1, x.rep)
}
x.block <- names(table(x.df$Cluster))[which(table(x.df$Cluster) == max(table(x.df$Cluster)))]
if(length(x.block) > 1){
x.block <- sample(size=1, x.block)
}
x.condition <- names(table(x.df$Age))[which(table(x.df$Age) == max(table(x.df$Age)))]
if(length(x.condition) > 1){
x.condition <- sample(size=1, x.condition)
}
tec.neighbour.list[[x]] <- data.frame("Replicate"=x.rep, "Cluster"=x.block, "Condition"=x.condition, "Neighbourhood"=x)
}
tec.neighbour.meta <- do.call(rbind.data.frame, tec.neighbour.list)
tec.neighbour.merge <- merge(tec.neighbor.df, tec.neighbour.meta, by='Neighbourhood')
tec.neighbour.merge$Diff <- sign(tec.neighbour.merge$logFC)
tec.neighbour.merge$Diff[tec.neighbour.merge$Sig == 0] <- 0
ggplot(tec.neighbour.merge, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=tec.neighbour.merge[, c("UMAP1", "UMAP2")],
colour='grey80', size=1) +
geom_point(data=tec.neighbour.merge[tec.neighbour.merge$Sig == 1, ],
aes(colour=logFC), size=4) +
theme_clean() +
scale_colour_gradient2(low="blue", mid="grey80", high="red") +
facet_wrap(~Cluster)

This extended analysis with k=11 also detects the increase in the small sTEC population. There also appears to be more heterogeneity in the Intertypical TEC, and, somewhat ingtriguingly, changes amongst the mature mTEC which were not detected originally.
table(tec.neighbour.merge$Cluster, tec.neighbour.merge$Diff)
-1 0 1
Mature mTEC 5 65 2
Proliferating TEC 11 6 0
Intertypical TEC 16 28 43
Perinatal cTEC 6 3 0
sTEC 0 0 1
Post-Aire mTEC 0 14 0
Mature cTEC 2 14 10
nTEC 0 3 0
Tuft-like mTEC 0 3 0
I would say that these results make a lot of sense. I’ll extend it to include the quadratic testing which should pick up the inverse-parabolic profile of the Post-Aire mTEC population.
quad.tec.res <- as.data.frame(topTags(glmQLFTest(tec.fit, coef=3), sort.by='none', n=Inf))
quad.tec.res$Sig <- as.factor(as.numeric(quad.tec.res$FDR <= 0.05))
quad.tec.res$Neighbourhood <- as.numeric(rownames(quad.tec.res))
# control the spatial FDR
qvals <- quad.tec.res$PValue
is.sig <- qvals <= 0.05
summary(is.sig)
Mode FALSE TRUE
logical 205 27
There are 27 DA neighbourhoods from the quadratic model - I expect these should reflect the Post-Aire mTEC.
quad.tec.spatialfdr <- graph_spatialFDR(neighborhoods=tec.vertex.list, graph=tec.knn, connectivity="distance",
pca=as.matrix(tec.sub.meta[, paste0("PC", 1:30)]),
pvalues=quad.tec.res[order(quad.tec.res$Neighbourhood), ]$PValue)
quad.tec.res$SpatialFDR[order(quad.tec.res$Neighbourhood)] <- quad.tec.spatialfdr
qvals <- quad.tec.spatialfdr
is.sig <- qvals <= 0.05
summary(is.sig)
Mode FALSE
logical 232
Dang, clearly still the small group of Post-Aire mTEC means this isn’t sufficiently sensitive after multiple-testing correction.
ggplot(tec.neighbour.merge, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=tec.neighbour.merge[, c("UMAP1", "UMAP2")],
colour='grey80', size=1) +
geom_point(data=tec.neighbour.merge,
aes(colour=Cluster), size=3) +
theme_clean() +
scale_colour_manual(values=inter.cols) +
facet_wrap(~Cluster)

Hmm, the Post-Aire mTEC don’t form a single neighbourhood on their own but rather 3 separate ones. I would say this possibly too granular.
Compositional effects
Firstly, are compositional effects a problem here, and secondly, does the refined sampling scheme handle this? I’ll set up a new simulation that has 2 clusters, only one of which contains differentially abundant neighbourhoods.
set.seed(42)
r.n <- 1000
n.dim <- 50
block1.cells <- 250
# select a set of eigen values for the covariance matrix of each block, say 50 eigenvalues?
block1.eigens <- sapply(1:n.dim, FUN=function(X) rexp(n=1, rate=abs(runif(n=1, min=0, max=50))))
block1.eigens <- block1.eigens[order(block1.eigens)]
block1.p <- qr.Q(qr(matrix(rnorm(block1.cells^2, mean=4, sd=0.01), block1.cells)))
block1.sigma <- crossprod(block1.p, block1.p*block1.eigens)
block1.gex <- abs(rmvnorm(n=r.n, mean=rnorm(n=block1.cells, mean=2, sd=0.01), sigma=block1.sigma))
block3.cells <- 250
# select a set of eigen values for the covariance matrix of each block, say 50 eigenvalues?
block3.eigens <- sapply(1:n.dim, FUN=function(X) rexp(n=1, rate=abs(runif(n=1, min=0, max=50))))
block3.eigens <- block3.eigens[order(block3.eigens)]
block3.p <- qr.Q(qr(matrix(rnorm(block3.cells^2, mean=4, sd=0.01), block3.cells)))
block3.sigma <- crossprod(block3.p, block3.p*block3.eigens)
block3.gex <- abs(rmvnorm(n=r.n, mean=rnorm(n=block3.cells, mean=5, sd=0.01), sigma=block3.sigma))
sim2.gex <- do.call(cbind, list("b1"=block1.gex, "b3"=block3.gex))
sim2.pca <- prcomp_irlba(t(sim2.gex), n=50, scale.=TRUE, center=TRUE)
pairs(sim2.pca$x[, c(1:5)])

I’ll use the reduced dimensions here to compute a KNN-graph and visualise it using a Fructerman-Reingold layout.
set.seed(42)
sim2.knn <- buildKNNGraph(x=sim2.pca$x[, c(1:30)], k=21, d=NA, transposed=TRUE)
sim2.fr.layout <- layout_with_fr(sim2.knn)
plot(sim2.knn, layout=sim2.fr.layout, vertex.frame.color='skyblue', vertex.color='skyblue', vertex.label.color='black',
vertex.label.family='Helvetica', edge.color='grey60', vertex.label.cex=0.9,
vertex.label.dist=1, edge.arrow.size=0.2)

Also a UMAP layout.
set.seed(42)
stem.ta.umap <- umap(sim2.pca$x[, c(1:30)],
n_components=2,
n_neighbors=21, metric='euclidean',
init='random', min_dist=0.1)
plot(stem.ta.umap$layout, col=c(rep("red", block1.cells), rep("orange", block3.cells)),
xlab="UMAP 1", ylab="UMAP 2")

Within each of these clouds of points I will randomly label 1:9 in block 1 and 1:1 in block 2.
set.seed(42)
block1.cond <- rep("A", block1.cells)
block1.a <- sample(1:block1.cells, size=floor(block1.cells*0.1))
block1.b <- setdiff(1:block1.cells, block1.a)
block1.cond[block1.b] <- "B"
block3.cond <- rep("A", block3.cells)
block3.a <- sample(1:block3.cells, size=floor(block3.cells*0.5))
block3.b <- setdiff(1:block3.cells, block3.a)
block3.cond[block3.b] <- "B"
meta.df <- data.frame("Block"=c(rep("B1", block1.cells), rep("B3", block3.cells)),
"Condition"=c(block1.cond, block3.cond),
"Replicate"=c(rep("R1", floor(block1.cells*0.33)), rep("R2", floor(block1.cells*0.33)),
rep("R3", block1.cells-(2*floor(block1.cells*0.33))),
rep("R1", floor(block3.cells*0.33)), rep("R2", floor(block3.cells*0.33)),
rep("R3", block3.cells-(2*floor(block3.cells*0.33)))))
meta.df <- cbind(meta.df, stem.ta.umap$layout)
colnames(meta.df) <- c("Block", "Condition", "Replicate", "UMAP1", "UMAP2")
# define a "sample" as teh combination of condition and replicate
meta.df$Sample <- paste(meta.df$Condition, meta.df$Replicate, sep="_")
meta.df$Vertex <- c(1:nrow(meta.df))
ggplot(meta.df, aes(x=UMAP1, y=UMAP2)) +
geom_point(aes(colour=Block, shape=Replicate)) +
theme_clean() +
scale_colour_npg() +
facet_wrap(~Condition) +
guides(colour=guide_legend(override.aes=list(size=3)),
shape=guide_legend(override.aes=list(size=3)))


The refined sampling scheme leads to large neighbourhoods overall - we think this might increase power and sensitivity as the counts in each will also be larger and therefore more stable.
Test using random sampling
sim2.counts <- quant_neighbourhood(graph=sim2.knn, meta=meta.df, sample.column='Sample', sample.vertices=n.hood)
sample.meta <- data.frame("Condition"=c(rep("A", 3), rep("B", 3)),
"Replicate"=rep(c("R1", "R2", "R3"), 2))
sample.meta$Sample <- paste(sample.meta$Condition, sample.meta$Replicate, sep="_")
rownames(sample.meta) <- sample.meta$Sample
# sim2.model <- model.matrix(~ 0 + Condition, data=sample.meta)
sim2.model <- model.matrix(~ Condition, data=sample.meta)
head(sim2.model)
(Intercept) ConditionB
A_R1 1 0
A_R2 1 0
A_R3 1 0
B_R1 1 1
B_R2 1 1
B_R3 1 1
I have a model matrix and counts matrix - let’s test edgeR on these.
count.means <- rowMeans(sim2.counts[, rownames(sim2.model)])
count.vars <- apply(sim2.counts[, rownames(sim2.model)], 1, var)
plot(count.means, count.vars)

The data are overdispersed. The model normalisation is causing some consternation. These all rely on normalising the neighbourhood counts by some factor. What if the normalisation uses the total number of cells in the experiment for each sample, rather than the counts in neighbourhoods, which will always be higher because cells are counted multiple times.
sim2.dge <- DGEList(sim2.counts[, rownames(sim2.model)], lib.size=log(colSums(sim2.counts[, rownames(sim2.model)])))
sim2.dge <- estimateDisp(sim2.dge, sim2.model)
sim2.fit <- glmQLFit(sim2.dge, sim2.model, robust=TRUE)
sim2.res <- as.data.frame(topTags(glmQLFTest(sim2.fit, coef=2), sort.by='none', n=Inf))
sim2.res$Neighbourhood <- as.numeric(rownames(sim2.res))
sim2.spatialfdr <- graph_spatialFDR(neighborhoods=vertex.list, graph=sim2.knn, connectivity="distance",
pvalues=sim2.res[order(sim2.res$Neighbourhood), ]$PValue,
pca=sim2.pca$x[, c(1:30)])
sim2.res$SpatialFDR[order(sim2.res$Neighbourhood)] <- sim2.spatialfdr
qvals <- sim2.spatialfdr
is.sig <- qvals <= 0.01
summary(is.sig)
Mode FALSE TRUE
logical 29 21
This is at a 1% FDR.
sim2.neighbour.exprs <- neighborhood_expression(vertex.list, sim2.gex)
Embed these hyperspheres with a PCA and UMAP.
sim2.neighbour.pca <- prcomp((t(sim2.neighbour.exprs)))
set.seed(42)
neighbourhood.umap <- umap(sim2.neighbour.pca$x[, c(1:30)],
n_components=2,
n_neighbors=21, metric='euclidean',
init='random', min_dist=0.1)
plot(neighbourhood.umap$layout,
xlab="UMAP 1", ylab="UMAP 2")

We can overlay the DA testing on these neighbourhoods.
neighbor.df <- sim2.res[, c("logFC", "Neighbourhood", "SpatialFDR")]
neighbor.df <- do.call(cbind.data.frame, list(neighbor.df, as.data.frame(neighbourhood.umap$layout)))
colnames(neighbor.df) <- c("logFC", "Neighbourhood", "SpatialFDR", "UMAP1", "UMAP2")
neighbor.df$Sig <- as.numeric(neighbor.df$SpatialFDR <= 0.01)
ggplot(neighbor.df, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=neighbor.df[neighbor.df$Sig == 0, ],
colour='grey80', size=2) +
geom_point(data=neighbor.df[neighbor.df$Sig == 1, ],
aes(colour=logFC), size=4) +
theme_clean() +
scale_colour_gradient2(low="blue", mid="grey80", high="red")

Is that a subtle compositional effect in the 1 neighbourhood that is depleted? That cluster should not contain any DA neighbourhoods.
neighbour.list <- list()
for(x in seq_along(1:length(vertex.list))){
x.df <- meta.df[meta.df$Vertex %in% vertex.list[[x]], ]
x.rep <- names(table(x.df$Replicate))[which(table(x.df$Replicate) == max(table(x.df$Replicate)))]
if(length(x.rep) > 1){
x.rep <- sample(size=1, x.rep)
}
x.block <- names(table(x.df$Block))[which(table(x.df$Block) == max(table(x.df$Block)))]
if(length(x.block) > 1){
x.block <- sample(size=1, x.block)
}
x.condition <- names(table(x.df$Condition))[which(table(x.df$Condition) == max(table(x.df$Condition)))]
if(length(x.condition) > 1){
x.condition <- sample(size=1, x.condition)
}
neighbour.list[[x]] <- data.frame("Replicate"=x.rep, "Block"=x.block, "Condition"=x.condition, "Neighbourhood"=x)
}
neighbour.meta <- do.call(rbind.data.frame, neighbour.list)
neighbour.merge <- merge(neighbor.df, neighbour.meta, by='Neighbourhood')
neighbour.merge$Block <- ordered(neighbour.merge$Block,
levels=c("B1", "B3"))
neighbour.merge$Diff <- sign(neighbour.merge$logFC)
neighbour.merge$Diff[neighbour.merge$Sig == 0] <- 0
ggplot(neighbour.merge, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=neighbour.merge[, c("UMAP1", "UMAP2")],
colour='grey80', size=1) +
geom_point(data=neighbour.merge[neighbour.merge$Sig == 1, ],
aes(colour=logFC), size=4) +
theme_clean() +
scale_colour_gradient2(low="blue", mid="grey80", high="red") +
facet_wrap(~Block)

This doesn’t make sense - Block 3 shouldn’t have any DA neighbourhoods. Is this a compositional effect we’re seeing here? It’s strange that a random fluctuation would cause this - it must be incredibly sensitive. This is also sample-size dependent, smaller total sample sizes are less susceptible for some reason.
table(neighbour.merge$Block, neighbour.merge$Diff)
-1 0 1
B1 0 0 20
B3 1 29 0
That single depleted neighbourhood in B3 is, I think, a compositional effect. Does the refined sampling deal with this in some way, either by having neighbourhoods with larger, and thus more stable counts?
all.samps <- unique(paste(meta.df$Block, meta.df$Condition, meta.df$Replicate, sep="_"))
meta.df$All.Sample <- paste(meta.df$Block, meta.df$Condition, meta.df$Replicate, sep="_")
all.count.matrix <- matrix(0L, ncol=length(all.samps), nrow=length(vertex.list))
colnames(all.count.matrix) <- all.samps
for(x in seq_along(1:length(vertex.list))){
v.x <- vertex.list[[x]]
for(i in seq_along(1:length(all.samps))){
i.s <- all.samps[i]
i.s.vertices <- intersect(v.x, meta.df[meta.df$All.Sample == i.s, ]$Vertex)
all.count.matrix[x, i] <- length(i.s.vertices)
}
}
all.count.melt <- melt(all.count.matrix)
all.count.melt$Var2 <- as.character(all.count.melt$Var2)
all.count.melt$Block <- unlist(lapply(strsplit(all.count.melt$Var2, split="_", fixed=TRUE),
FUN=function(XP) paste0(XP[1])))
all.count.melt$Condition <- unlist(lapply(strsplit(all.count.melt$Var2, split="_", fixed=TRUE),
FUN=function(XP) paste0(XP[2])))
all.count.melt$Replicate <- unlist(lapply(strsplit(all.count.melt$Var2, split="_", fixed=TRUE),
FUN=function(XP) paste0(XP[3])))
ggplot(all.count.melt[all.count.melt$Var1 %in% c(neighbour.merge$Neighbourhood[neighbour.merge$Sig == 1]) &
all.count.melt$Block %in% c("B3"), ],
aes(x=Block, y=value, fill=Condition)) +
geom_boxplot() +
theme_clean() +
facet_wrap(~Var1, scales="free_y")

These are the counts for B3 in the DANs. N21 looks like the effect is from sampling variance, as the counts are really quite low.
Test using refined sampling
refine_vertex <- function(vertex.knn, v.ix, X_pca){
# vertex.knn: KNN graph for randomly sampled points (output of BiocNeighbors::findKNN)
# v.ix: index of vertex to refine in vertex.knn
## Calculate median profile of KNNs of vertex
v.med <- apply(X_pca[vertex.knn$index[v.ix,],], 2, median)
## Find the closest point to the median and sample
refined.vertex <- BiocNeighbors::findKNN(rbind(v.med, X_pca), subset=1, k=1)[["index"]][1] - 1 ## -1 to remove the median
return(refined.vertex)
}
quant_neighbourhood <- function(graph, meta, sample.column='Sample', sample.vertices=0.25, seed=42, pca=NULL, sample="random"){
set.seed(seed)
if(sample == "random"){
# define a set of vertices and neihbourhood centers - extract the neihbourhoods of these cells
random.vertices <- sample(V(graph), size=floor(sample.vertices*length(V(graph))))
vertex.list <- sapply(1:length(random.vertices), FUN=function(X) neighbors(graph, v=random.vertices[X]))
} else if(sample == "refined"){
if(is.null(pca)){
stop("Please pass a PCA object - expected output from prcomp()")
}
X_pca <- pca$x[, c(1:30)]
random.vertices <- sample(V(graph), size=floor(sample.vertices*length(V(graph))))
vertex.knn <- BiocNeighbors::findKNN(X=X_pca, k=21, subset=as.vector(random.vertices))
refined.vertices <- V(graph)[sapply(1:nrow(vertex.knn$index), function(i) refine_vertex(vertex.knn, i, X_pca))]
vertex.list <- sapply(1:length(random.vertices), FUN=function(X) neighbors(graph, v=random.vertices[X]))
vertex.list.refined <- sapply(1:length(refined.vertices), FUN=function(X) neighbors(graph, v=refined.vertices[X]))
vertex.list <- vertex.list.refined
}
count.matrix <- matrix(0L, ncol=length(unique(meta[, sample.column])), nrow=length(vertex.list))
colnames(count.matrix) <- unique(meta[, sample.column])
for(x in seq_along(1:length(vertex.list))){
v.x <- vertex.list[[x]]
for(i in seq_along(1:length(unique(meta[, sample.column])))){
i.s <- unique(meta[, sample.column])[i]
i.s.vertices <- intersect(v.x, meta[meta[, sample.column] == i.s, ]$Vertex)
count.matrix[x, i] <- length(i.s.vertices)
}
}
rownames(count.matrix) <- c(1:length(vertex.list))
return(count.matrix)
}
sim2.counts <- quant_neighbourhood(graph=sim2.knn, meta=meta.df, sample.column='Sample', sample.vertices=n.hood, sample="refined", pca=sim2.pca)
sample.meta <- data.frame("Condition"=c(rep("A", 3), rep("B", 3)),
"Replicate"=rep(c("R1", "R2", "R3"), 2))
sample.meta$Sample <- paste(sample.meta$Condition, sample.meta$Replicate, sep="_")
rownames(sample.meta) <- sample.meta$Sample
# sim2.model <- model.matrix(~ 0 + Condition, data=sample.meta)
sim2.model <- model.matrix(~ Condition, data=sample.meta)
head(sim2.model)
(Intercept) ConditionB
A_R1 1 0
A_R2 1 0
A_R3 1 0
B_R1 1 1
B_R2 1 1
B_R3 1 1
I have a model matrix and counts matrix - let’s test edgeR on these.
sim2.dge <- DGEList(sim2.counts[, rownames(sim2.model)], lib.size=log(colSums(sim2.counts[, rownames(sim2.model)])))
sim2.dge <- estimateDisp(sim2.dge, sim2.model, tagwise=TRUE)
sim2.fit <- glmQLFit(sim2.dge, sim2.model, robust=TRUE)
# sim2.contrast <- makeContrasts(ConditionA - ConditionB, levels=sim2.model)
# sim2.res <- glmQLFTest(sim2.fit, contrast=sim2.contrast)
sim2.res <- as.data.frame(topTags(glmQLFTest(sim2.fit, coef=2), sort.by='none', n=Inf))
sim2.res$Neighbourhood <- as.numeric(rownames(sim2.res))
sim2.spatialfdr <- graph_spatialFDR(neighborhoods=vertex.list.refined, graph=sim2.knn, connectivity="distance",
pvalues=sim2.res[order(sim2.res$Neighbourhood), ]$PValue,
pca=sim2.pca$x[, c(1:30)])
sim2.res$SpatialFDR[order(sim2.res$Neighbourhood)] <- sim2.spatialfdr
qvals <- sim2.spatialfdr
is.sig <- qvals <= 0.01
summary(is.sig)
Mode FALSE TRUE
logical 30 20
That’s a lot of DA neigbbourhoods!
sim2.neighbour.exprs <- neighborhood_expression(vertex.list.refined, sim2.gex)
Embed these hyperspheres with a PCA and UMAP.
sim2.neighbour.pca <- prcomp((t(sim2.neighbour.exprs)))
set.seed(42)
neighbourhood.umap <- umap(sim2.neighbour.pca$x[, c(1:30)],
n_components=2,
n_neighbors=21, metric='euclidean',
init='random', min_dist=0.1)
plot(neighbourhood.umap$layout,
xlab="UMAP 1", ylab="UMAP 2")

We can overlay the DA testing on these neighbourhoods.
neighbor.df <- sim2.res[, c("logFC", "Neighbourhood", "SpatialFDR")]
neighbor.df <- do.call(cbind.data.frame, list(neighbor.df, as.data.frame(neighbourhood.umap$layout)))
colnames(neighbor.df) <- c("logFC", "Neighbourhood", "SpatialFDR", "UMAP1", "UMAP2")
neighbor.df$Sig <- as.numeric(neighbor.df$SpatialFDR <= 0.01)
ggplot(neighbor.df, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=neighbor.df[neighbor.df$Sig == 0, ],
colour='grey80', size=2) +
geom_point(data=neighbor.df[neighbor.df$Sig == 1, ],
aes(colour=logFC), size=4) +
theme_clean() +
scale_colour_gradient2(low="blue", mid="grey80", high="red")

No more false DANs in B3, despite the same number of neighbourhoods being counted.
neighbour.list <- list()
for(x in seq_along(1:length(vertex.list.refined))){
x.df <- meta.df[meta.df$Vertex %in% vertex.list.refined[[x]], ]
x.rep <- names(table(x.df$Replicate))[which(table(x.df$Replicate) == max(table(x.df$Replicate)))]
if(length(x.rep) > 1){
x.rep <- sample(size=1, x.rep)
}
x.block <- names(table(x.df$Block))[which(table(x.df$Block) == max(table(x.df$Block)))]
if(length(x.block) > 1){
x.block <- sample(size=1, x.block)
}
x.condition <- names(table(x.df$Condition))[which(table(x.df$Condition) == max(table(x.df$Condition)))]
if(length(x.condition) > 1){
x.condition <- sample(size=1, x.condition)
}
neighbour.list[[x]] <- data.frame("Replicate"=x.rep, "Block"=x.block, "Condition"=x.condition, "Neighbourhood"=x)
}
neighbour.meta <- do.call(rbind.data.frame, neighbour.list)
neighbour.merge <- merge(neighbor.df, neighbour.meta, by='Neighbourhood')
neighbour.merge$Block <- ordered(neighbour.merge$Block,
levels=c("B1", "B3"))
neighbour.merge$Diff <- sign(neighbour.merge$logFC)
neighbour.merge$Diff[neighbour.merge$Sig == 0] <- 0
ggplot(neighbour.merge, aes(x=UMAP1, y=UMAP2)) +
geom_point(data=neighbour.merge[, c("UMAP1", "UMAP2")],
colour='grey80', size=1) +
geom_point(data=neighbour.merge[neighbour.merge$Sig == 1, ],
aes(colour=logFC), size=4) +
theme_clean() +
scale_colour_gradient2(low="blue", mid="grey80", high="red") +
facet_wrap(~Block)

This doesn’t make sense - Block 3 shouldn’t have any DA neighbourhoods. Is this a compositional effect we’re seeing here? It’s strange that a random fluctuation would cause this - it must be incredibly sensitive.
table(neighbour.merge$Block, neighbour.merge$Diff)
0 1
B1 0 20
B3 30 0
all.samps <- unique(paste(meta.df$Block, meta.df$Condition, meta.df$Replicate, sep="_"))
meta.df$All.Sample <- paste(meta.df$Block, meta.df$Condition, meta.df$Replicate, sep="_")
all.count.matrix <- matrix(0L, ncol=length(all.samps), nrow=length(vertex.list.refined))
colnames(all.count.matrix) <- all.samps
for(x in seq_along(1:length(vertex.list.refined))){
v.x <- vertex.list.refined[[x]]
for(i in seq_along(1:length(all.samps))){
i.s <- all.samps[i]
i.s.vertices <- intersect(v.x, meta.df[meta.df$All.Sample == i.s, ]$Vertex)
all.count.matrix[x, i] <- length(i.s.vertices)
}
}
all.count.melt <- melt(all.count.matrix)
all.count.melt$Var2 <- as.character(all.count.melt$Var2)
all.count.melt$Block <- unlist(lapply(strsplit(all.count.melt$Var2, split="_", fixed=TRUE),
FUN=function(XP) paste0(XP[1])))
all.count.melt$Condition <- unlist(lapply(strsplit(all.count.melt$Var2, split="_", fixed=TRUE),
FUN=function(XP) paste0(XP[2])))
all.count.melt$Replicate <- unlist(lapply(strsplit(all.count.melt$Var2, split="_", fixed=TRUE),
FUN=function(XP) paste0(XP[3])))
ggplot(all.count.melt[all.count.melt$Var1 %in% c(neighbour.merge$Neighbourhood[neighbour.merge$Sig == 1]) &
all.count.melt$Block %in% c("B3"), ],
aes(x=Block, y=value, fill=Condition)) +
geom_boxplot() +
theme_clean() +
facet_wrap(~Var1, scales="free_y")

LS0tCnRpdGxlOiAiTWlsbzogU2ltdWxhdGlvbnMgZm9yIGRpc2NyZXRlIGNhc2VzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIEludHJvZHVjdGlvbgoKR2VuZXJhdGluZyBzaW11bGF0aW9ucyBhbmQgdGhlbiB1c2luZyB0aGUgdGh5bXVzIGFnZWluZyBkYXRhIHRvIHRlc3QgZGlmZmVyZW50aWFsIGFidW5kYW5jZSB0ZXN0aW5nIG9uIGEgZ3JhcGgvdHJlZS4KCmBgYHtyLCBlY2hvPVRSVUUsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShlZGdlUikKbGlicmFyeShpZ3JhcGgpCmxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCmxpYnJhcnkoc2NyYW4pCmxpYnJhcnkoc2NhdGVyKQpsaWJyYXJ5KGlybGJhKQpsaWJyYXJ5KGdndGhlbWVzKQpsaWJyYXJ5KGdnc2NpKQpsaWJyYXJ5KGN5ZGFyKQpsaWJyYXJ5KG12dG5vcm0pCmxpYnJhcnkodW1hcCkKbGlicmFyeShyZXNoYXBlMikKYGBgCgojIyBTaW11bGF0aW9uIDEKCkknbGwgc3RhcnQgYnkgc2ltdWxhdGluZyAzIGNsb3VkcyBvZiBwb2ludHMgaW4gJFxtYXRoYmJ7Un1ee259JCwgZWFjaCBjb25zaXN0aW5nIG9mIHBvaW50cyBmcm9tIDIgcG9vbHMsIEEgJiBCLiBFYWNoIGNsb3VkIG9mIHBvaW50cyB3aWxsIGJlIGNvbXBvc2VkIG9mOgoKKiBBOiAxMCUsIEI6IDkwJQoqIEE6IDkwJSwgQjogMTAlCiogQTogNTAlLCBCOiA1MCUKCkkgd2lsbCB0aGVuIHBlcmZvcm0gYSBQQ0Egb24gdGhlIG1hdHJpeCBvZiB0aGVzZSBwb2ludHMgYW5kIGNvbnN0cnVjdCBhIEtOTi1ncmFwaCwgbWltaWNpbmcgdGhlIHN0YW5kYXJkIHdvcmtmbG93IG9mIG1hbnkgc2NSTkEtc2VxIGFuYWx5c2VzLgoKYGBge3IsIGVjaG89VFJVRSwgd2FybmluZz1GQUxTRX0Kc2V0LnNlZWQoNDIpCnIubiA8LSAxMDAwCm4uZGltIDwtIDUwCmJsb2NrMS5jZWxscyA8LSAxMjAwCiMgc2VsZWN0IGEgc2V0IG9mIGVpZ2VuIHZhbHVlcyBmb3IgdGhlIGNvdmFyaWFuY2UgbWF0cml4IG9mIGVhY2ggYmxvY2ssIHNheSA1MCBlaWdlbnZhbHVlcz8KYmxvY2sxLmVpZ2VucyA8LSBzYXBwbHkoMTpuLmRpbSwgRlVOPWZ1bmN0aW9uKFgpIHJleHAobj0xLCByYXRlPWFicyhydW5pZihuPTEsIG1pbj0wLCBtYXg9NTApKSkpCmJsb2NrMS5laWdlbnMgPC0gYmxvY2sxLmVpZ2Vuc1tvcmRlcihibG9jazEuZWlnZW5zKV0KYmxvY2sxLnAgPC0gcXIuUShxcihtYXRyaXgocm5vcm0oYmxvY2sxLmNlbGxzXjIsIG1lYW49NCwgc2Q9MC4wMSksIGJsb2NrMS5jZWxscykpKQpibG9jazEuc2lnbWEgPC0gY3Jvc3Nwcm9kKGJsb2NrMS5wLCBibG9jazEucCpibG9jazEuZWlnZW5zKQpibG9jazEuZ2V4IDwtIGFicyhybXZub3JtKG49ci5uLCBtZWFuPXJub3JtKG49YmxvY2sxLmNlbGxzLCBtZWFuPTIsIHNkPTAuMDEpLCBzaWdtYT1ibG9jazEuc2lnbWEpKQoKCmJsb2NrMi5jZWxscyA8LSAxMjAwCiMgc2VsZWN0IGEgc2V0IG9mIGVpZ2VuIHZhbHVlcyBmb3IgdGhlIGNvdmFyaWFuY2UgbWF0cml4IG9mIGVhY2ggYmxvY2ssIHNheSA1MCBlaWdlbnZhbHVlcz8KYmxvY2syLmVpZ2VucyA8LSBzYXBwbHkoMTpuLmRpbSwgRlVOPWZ1bmN0aW9uKFgpIHJleHAobj0xLCByYXRlPWFicyhydW5pZihuPTEsIG1pbj0wLCBtYXg9NTApKSkpCmJsb2NrMi5laWdlbnMgPC0gYmxvY2syLmVpZ2Vuc1tvcmRlcihibG9jazIuZWlnZW5zKV0KYmxvY2syLnAgPC0gcXIuUShxcihtYXRyaXgocm5vcm0oYmxvY2syLmNlbGxzXjIsIG1lYW49NCwgc2Q9MC4wMSksIGJsb2NrMi5jZWxscykpKQpibG9jazIuc2lnbWEgPC0gY3Jvc3Nwcm9kKGJsb2NrMi5wLCBibG9jazIucCpibG9jazIuZWlnZW5zKQpibG9jazIuZ2V4IDwtIGFicyhybXZub3JtKG49ci5uLCBtZWFuPXJub3JtKG49YmxvY2syLmNlbGxzLCBtZWFuPTQsIHNkPTAuMDEpLCBzaWdtYT1ibG9jazIuc2lnbWEpKQoKCmJsb2NrMy5jZWxscyA8LSAxMjUwCiMgc2VsZWN0IGEgc2V0IG9mIGVpZ2VuIHZhbHVlcyBmb3IgdGhlIGNvdmFyaWFuY2UgbWF0cml4IG9mIGVhY2ggYmxvY2ssIHNheSA1MCBlaWdlbnZhbHVlcz8KYmxvY2szLmVpZ2VucyA8LSBzYXBwbHkoMTpuLmRpbSwgRlVOPWZ1bmN0aW9uKFgpIHJleHAobj0xLCByYXRlPWFicyhydW5pZihuPTEsIG1pbj0wLCBtYXg9NTApKSkpCmJsb2NrMy5laWdlbnMgPC0gYmxvY2szLmVpZ2Vuc1tvcmRlcihibG9jazMuZWlnZW5zKV0KYmxvY2szLnAgPC0gcXIuUShxcihtYXRyaXgocm5vcm0oYmxvY2szLmNlbGxzXjIsIG1lYW49NCwgc2Q9MC4wMSksIGJsb2NrMy5jZWxscykpKQpibG9jazMuc2lnbWEgPC0gY3Jvc3Nwcm9kKGJsb2NrMy5wLCBibG9jazMucCpibG9jazMuZWlnZW5zKQpibG9jazMuZ2V4IDwtIGFicyhybXZub3JtKG49ci5uLCBtZWFuPXJub3JtKG49YmxvY2szLmNlbGxzLCBtZWFuPTUsIHNkPTAuMDEpLCBzaWdtYT1ibG9jazMuc2lnbWEpKQoKc2ltMS5nZXggPC0gZG8uY2FsbChjYmluZCwgbGlzdCgiYjEiPWJsb2NrMS5nZXgsICJiMiI9YmxvY2syLmdleCwgImIzIj1ibG9jazMuZ2V4KSkKYGBgCgoKYGBge3J9CnNpbTEucGNhIDwtIHByY29tcF9pcmxiYSh0KHNpbTEuZ2V4KSwgbj01MCwgc2NhbGUuPVRSVUUsIGNlbnRlcj1UUlVFKQpwYWlycyhzaW0xLnBjYSR4WywgYygxOjUpXSkKYGBgCgpJJ2xsIHVzZSB0aGUgcmVkdWNlZCBkaW1lbnNpb25zIGhlcmUgdG8gY29tcHV0ZSBhIEtOTi1ncmFwaCBhbmQgdmlzdWFsaXNlIGl0IHVzaW5nIGEgRnJ1Y3Rlcm1hbi1SZWluZ29sZCBsYXlvdXQuCgpgYGB7cn0Kc2V0LnNlZWQoNDIpCnNpbTEua25uIDwtIGJ1aWxkS05OR3JhcGgoeD1zaW0xLnBjYSR4WywgYygxOjMwKV0sIGs9MjEsIGQ9TkEsIHRyYW5zcG9zZWQ9VFJVRSkKc2ltMS5mci5sYXlvdXQgPC0gbGF5b3V0X3dpdGhfZnIoc2ltMS5rbm4pCnBsb3Qoc2ltMS5rbm4sIGxheW91dD1zaW0xLmZyLmxheW91dCwgdmVydGV4LmZyYW1lLmNvbG9yPSdza3libHVlJywgdmVydGV4LmNvbG9yPSdza3libHVlJywgdmVydGV4LmxhYmVsLmNvbG9yPSdibGFjaycsIAogICAgIHZlcnRleC5sYWJlbC5mYW1pbHk9J0hlbHZldGljYScsIGVkZ2UuY29sb3I9J2dyZXk2MCcsIHZlcnRleC5sYWJlbC5jZXg9MC45LAogICAgIHZlcnRleC5sYWJlbC5kaXN0PTEsIGVkZ2UuYXJyb3cuc2l6ZT0wLjIpCmBgYAoKQWxzbyBhIFVNQVAgbGF5b3V0LgoKYGBge3J9CnNldC5zZWVkKDQyKQpzdGVtLnRhLnVtYXAgPC0gdW1hcChzaW0xLnBjYSR4WywgYygxOjMwKV0sCiAgICAgICAgICAgICAgICAgICAgIG5fY29tcG9uZW50cz0yLAogICAgICAgICAgICAgICAgICAgICBuX25laWdoYm9ycz0yMSwgbWV0cmljPSdldWNsaWRlYW4nLAogICAgICAgICAgICAgICAgICAgICBpbml0PSdyYW5kb20nLCBtaW5fZGlzdD0wLjEpCnBsb3Qoc3RlbS50YS51bWFwJGxheW91dCwgY29sPWMocmVwKCJyZWQiLCBibG9jazEuY2VsbHMpLCByZXAoInNreWJsdWUiLCBibG9jazIuY2VsbHMpLCByZXAoIm9yYW5nZSIsIGJsb2NrMy5jZWxscykpLAogICAgIHhsYWI9IlVNQVAgMSIsIHlsYWI9IlVNQVAgMiIpCmBgYAoKV2l0aGluIGVhY2ggb2YgdGhlc2UgY2xvdWRzIG9mIHBvaW50cyBJIHdpbGwgcmFuZG9tbHkgbGFiZWwgaW4gdGhlIHByb3BvcnRpb25zIGFib3ZlLgoKYGBge3J9CnNldC5zZWVkKDQyKQpibG9jazEuY29uZCA8LSByZXAoIkEiLCBibG9jazEuY2VsbHMpCmJsb2NrMS5hIDwtIHNhbXBsZSgxOmJsb2NrMS5jZWxscywgc2l6ZT1mbG9vcihibG9jazEuY2VsbHMqMC45KSkKYmxvY2sxLmIgPC0gc2V0ZGlmZigxOmJsb2NrMS5jZWxscywgYmxvY2sxLmEpCmJsb2NrMS5jb25kW2Jsb2NrMS5iXSA8LSAiQiIKCmJsb2NrMi5jb25kIDwtIHJlcCgiQSIsIGJsb2NrMi5jZWxscykKYmxvY2syLmEgPC0gc2FtcGxlKDE6YmxvY2syLmNlbGxzLCBzaXplPWZsb29yKGJsb2NrMi5jZWxscyowLjA1KSkKYmxvY2syLmIgPC0gc2V0ZGlmZigxOmJsb2NrMi5jZWxscywgYmxvY2syLmEpCmJsb2NrMi5jb25kW2Jsb2NrMi5iXSA8LSAiQiIKCmJsb2NrMy5jb25kIDwtIHJlcCgiQSIsIGJsb2NrMy5jZWxscykKYmxvY2szLmEgPC0gc2FtcGxlKDE6YmxvY2szLmNlbGxzLCBzaXplPWZsb29yKGJsb2NrMy5jZWxscyowLjUpKQpibG9jazMuYiA8LSBzZXRkaWZmKDE6YmxvY2szLmNlbGxzLCBibG9jazMuYSkKYmxvY2szLmNvbmRbYmxvY2szLmJdIDwtICJCIgoKbWV0YS5kZiA8LSBkYXRhLmZyYW1lKCJCbG9jayI9YyhyZXAoIkIxIiwgYmxvY2sxLmNlbGxzKSwgcmVwKCJCMiIsIGJsb2NrMi5jZWxscyksIHJlcCgiQjMiLCBibG9jazMuY2VsbHMpKSwKICAgICAgICAgICAgICAgICAgICAgICJDb25kaXRpb24iPWMoYmxvY2sxLmNvbmQsIGJsb2NrMi5jb25kLCBibG9jazMuY29uZCksCiAgICAgICAgICAgICAgICAgICAgICAiUmVwbGljYXRlIj1jKHJlcCgiUjEiLCBmbG9vcihibG9jazEuY2VsbHMqMC4zMykpLCByZXAoIlIyIiwgZmxvb3IoYmxvY2sxLmNlbGxzKjAuMzMpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcCgiUjMiLCBibG9jazEuY2VsbHMtKDIqZmxvb3IoYmxvY2sxLmNlbGxzKjAuMzMpKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcCgiUjEiLCBmbG9vcihibG9jazIuY2VsbHMqMC4zMykpLCByZXAoIlIyIiwgZmxvb3IoYmxvY2syLmNlbGxzKjAuMzMpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcCgiUjMiLCBibG9jazIuY2VsbHMtKDIqZmxvb3IoYmxvY2syLmNlbGxzKjAuMzMpKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcCgiUjEiLCBmbG9vcihibG9jazMuY2VsbHMqMC4zMykpLCByZXAoIlIyIiwgZmxvb3IoYmxvY2szLmNlbGxzKjAuMzMpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcCgiUjMiLCBibG9jazMuY2VsbHMtKDIqZmxvb3IoYmxvY2szLmNlbGxzKjAuMzMpKSkpKQptZXRhLmRmIDwtIGNiaW5kKG1ldGEuZGYsIHN0ZW0udGEudW1hcCRsYXlvdXQpCmNvbG5hbWVzKG1ldGEuZGYpIDwtIGMoIkJsb2NrIiwgIkNvbmRpdGlvbiIsICJSZXBsaWNhdGUiLCAiVU1BUDEiLCAiVU1BUDIiKQojIGRlZmluZSBhICJzYW1wbGUiIGFzIHRlaCBjb21iaW5hdGlvbiBvZiBjb25kaXRpb24gYW5kIHJlcGxpY2F0ZQptZXRhLmRmJFNhbXBsZSA8LSBwYXN0ZShtZXRhLmRmJENvbmRpdGlvbiwgbWV0YS5kZiRSZXBsaWNhdGUsIHNlcD0iXyIpCm1ldGEuZGYkVmVydGV4IDwtIGMoMTpucm93KG1ldGEuZGYpKQpgYGAKCgpgYGB7cn0KZ2dwbG90KG1ldGEuZGYsIGFlcyh4PVVNQVAxLCB5PVVNQVAyKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG91cj1CbG9jaywgc2hhcGU9UmVwbGljYXRlKSkgKwogIHRoZW1lX2NsZWFuKCkgKwogIHNjYWxlX2NvbG91cl9ucGcoKSArCiAgZmFjZXRfd3JhcCh+Q29uZGl0aW9uKSArCiAgZ3VpZGVzKGNvbG91cj1ndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzPWxpc3Qoc2l6ZT0zKSksCiAgICAgICAgIHNoYXBlPWd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXM9bGlzdChzaXplPTMpKSkKYGBgCgpVc2luZyB0aGVzZSBzaW11bGF0ZWQgZGF0YSB3ZSBjYW4gZGVmaW5lIGxvdHMgb2YgbmVpZ2hib3VyaG9vZHMgYWNyb3NzIHRoZSBncmFwaCB0byBjcmVhdGUgYSBjb3VudHMgbWF0cml4IG9mIG5laWdoYm91cmhvb2RzIHZzIGNvbmRpdGlvbnMgCmluIGEgc2ltaWxhciB3YXkgYXMgYGN5ZGFyYC4gSSdsbCBzdGFydCB3aXRoIHJhbmRvbSB2ZXJ0aWNlcyBpbiB0aGUgZ3JhcGggbWFraW5nIHVwIDUlIG9mIGFsbCBwb2ludHMgYW5kIGV4dHJhY3QgdGhlIGdyYXBoIG5laWdoYm9yaG9vZHMuCgpgYGB7cn0KIyByYW5kb21seSBzZWxlY3QgdmVydGljZXMgaW4gdGhlIGdyYXBoCm4uaG9vZCA8LSAwLjA1CnJhbmRvbS52ZXJ0aWNlcyA8LSBzYW1wbGUoVihzaW0xLmtubiksIHNpemU9Zmxvb3Iobi5ob29kKmxlbmd0aChWKHNpbTEua25uKSkpKQojIGxvb3Agb3ZlciByYW5kb20gdmVydGljZXMgYW5kIGNvdW50IHRoZSBudW1iZXIgb2YgY2VsbHMgaW4gZWFjaAp2ZXJ0ZXgubGlzdCA8LSBzYXBwbHkoMTpsZW5ndGgocmFuZG9tLnZlcnRpY2VzKSwgRlVOPWZ1bmN0aW9uKFgpIG5laWdoYm9ycyhzaW0xLmtubiwgdj1yYW5kb20udmVydGljZXNbWF0pKQpoaXN0KHVubGlzdChsYXBwbHkodmVydGV4Lmxpc3QsIGxlbmd0aCkpLCAxMDAsIG1haW49Ikhpc3RvZ3JhbSBvZiBuZWlnaGJvcnMiLCB4bGFiPSJOZWlnaGJvdXJob29kIHNpemUiKQpgYGAKCkZvciBlYWNoIG5laWdoYm91cmhvb2QgSSdsbCBjb3VudCB0aGUgbnVtYmVyIG9mIGNlbGxzIGluIGVhY2gsIGRldGVybWluZWQgYnkgdGhlIGV4cGVyaW1lbnRhbCBkZXNpZ24sIGkuZS4gcmVwbGljYXRlLCBjb25kaXRpb24gYW5kIGJsb2NrLgoKYGBge3J9CnF1YW50X25laWdoYm91cmhvb2QgPC0gZnVuY3Rpb24oZ3JhcGgsIG1ldGEsIHNhbXBsZS5jb2x1bW49J1NhbXBsZScsIHNhbXBsZS52ZXJ0aWNlcz0wLjI1LCBzZWVkPTQyKXsKICBzZXQuc2VlZChzZWVkKQogICMgZGVmaW5lIGEgc2V0IG9mIHZlcnRpY2VzIGFuZCBuZWloYm91cmhvb2QgY2VudGVycyAtIGV4dHJhY3QgdGhlIG5laWhib3VyaG9vZHMgb2YgdGhlc2UgY2VsbHMKICByYW5kb20udmVydGljZXMgPC0gc2FtcGxlKFYoZ3JhcGgpLCBzaXplPWZsb29yKHNhbXBsZS52ZXJ0aWNlcypsZW5ndGgoVihncmFwaCkpKSkKICB2ZXJ0ZXgubGlzdCA8LSBzYXBwbHkoMTpsZW5ndGgocmFuZG9tLnZlcnRpY2VzKSwgRlVOPWZ1bmN0aW9uKFgpIG5laWdoYm9ycyhncmFwaCwgdj1yYW5kb20udmVydGljZXNbWF0pKQogIAogIGNvdW50Lm1hdHJpeCA8LSBtYXRyaXgoMEwsIG5jb2w9bGVuZ3RoKHVuaXF1ZShtZXRhWywgc2FtcGxlLmNvbHVtbl0pKSwgbnJvdz1sZW5ndGgodmVydGV4Lmxpc3QpKQogIGNvbG5hbWVzKGNvdW50Lm1hdHJpeCkgPC0gdW5pcXVlKG1ldGFbLCBzYW1wbGUuY29sdW1uXSkKICAKICBmb3IoeCBpbiBzZXFfYWxvbmcoMTpsZW5ndGgodmVydGV4Lmxpc3QpKSl7CiAgICB2LnggPC0gdmVydGV4Lmxpc3RbW3hdXQogICAgZm9yKGkgaW4gc2VxX2Fsb25nKDE6bGVuZ3RoKHVuaXF1ZShtZXRhWywgc2FtcGxlLmNvbHVtbl0pKSkpewogICAgICBpLnMgPC0gdW5pcXVlKG1ldGFbLCBzYW1wbGUuY29sdW1uXSlbaV0KICAgICAgaS5zLnZlcnRpY2VzIDwtIGludGVyc2VjdCh2LngsIG1ldGFbbWV0YVssIHNhbXBsZS5jb2x1bW5dID09IGkucywgXSRWZXJ0ZXgpCiAgICAgIGNvdW50Lm1hdHJpeFt4LCBpXSA8LSBsZW5ndGgoaS5zLnZlcnRpY2VzKQogICAgfQogIH0KICByb3duYW1lcyhjb3VudC5tYXRyaXgpIDwtIGMoMTpsZW5ndGgodmVydGV4Lmxpc3QpKQogIHJldHVybihjb3VudC5tYXRyaXgpCn0KYGBgCgoKYGBge3J9CiMgZGVmaW5lIGEgc2V0IG9mIHZlcnRpY2VzIGFuZCBuZWloYm91cmhvb2QgY2VudGVycyAtIGV4dHJhY3QgdGhlIG5laWhib3VyaG9vZHMgb2YgdGhlc2UgY2VsbHMKc2V0LnNlZWQoNDIpCnJhbmRvbS52ZXJ0aWNlcyA8LSBzYW1wbGUoVihzaW0xLmtubiksIHNpemU9Zmxvb3Iobi5ob29kKmxlbmd0aChWKHNpbTEua25uKSkpKQp2ZXJ0ZXgubGlzdCA8LSBzYXBwbHkoMTpsZW5ndGgocmFuZG9tLnZlcnRpY2VzKSwgRlVOPWZ1bmN0aW9uKFgpIG5laWdoYm9ycyhzaW0xLmtubiwgdj1yYW5kb20udmVydGljZXNbWF0pKQoKY291bnQubWF0cml4IDwtIG1hdHJpeCgwTCwgbmNvbD1sZW5ndGgodW5pcXVlKG1ldGEuZGZbLCAiU2FtcGxlIl0pKSwgbnJvdz1sZW5ndGgodmVydGV4Lmxpc3QpKQpjb2xuYW1lcyhjb3VudC5tYXRyaXgpIDwtIHVuaXF1ZShtZXRhLmRmWywgIlNhbXBsZSJdKQoKZm9yKHggaW4gc2VxX2Fsb25nKDE6bGVuZ3RoKHZlcnRleC5saXN0KSkpewogIHYueCA8LSB2ZXJ0ZXgubGlzdFtbeF1dCiAgZm9yKGkgaW4gc2VxX2Fsb25nKDE6bGVuZ3RoKHVuaXF1ZShtZXRhLmRmWywgIlNhbXBsZSJdKSkpKXsKICAgIGkucyA8LSB1bmlxdWUobWV0YS5kZlssICJTYW1wbGUiXSlbaV0KICAgIGkucy52ZXJ0aWNlcyA8LSBpbnRlcnNlY3Qodi54LCBtZXRhLmRmW21ldGEuZGZbLCAiU2FtcGxlIl0gPT0gaS5zLCBdJFZlcnRleCkKICAgIGNvdW50Lm1hdHJpeFt4LCBpXSA8LSBsZW5ndGgoaS5zLnZlcnRpY2VzKQogIH0KfQpyb3duYW1lcyhjb3VudC5tYXRyaXgpIDwtIGMoMTpsZW5ndGgodmVydGV4Lmxpc3QpKQpgYGAKCgpgYGB7cn0Kc2ltMS5jb3VudHMgPC0gcXVhbnRfbmVpZ2hib3VyaG9vZChncmFwaD1zaW0xLmtubiwgbWV0YT1tZXRhLmRmLCBzYW1wbGUuY29sdW1uPSdTYW1wbGUnLCBzYW1wbGUudmVydGljZXM9bi5ob29kKQpzYW1wbGUubWV0YSA8LSBkYXRhLmZyYW1lKCJDb25kaXRpb24iPWMocmVwKCJBIiwgMyksIHJlcCgiQiIsIDMpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAiUmVwbGljYXRlIj1yZXAoYygiUjEiLCAiUjIiLCAiUjMiKSwgMikpCnNhbXBsZS5tZXRhJFNhbXBsZSA8LSBwYXN0ZShzYW1wbGUubWV0YSRDb25kaXRpb24sIHNhbXBsZS5tZXRhJFJlcGxpY2F0ZSwgc2VwPSJfIikKcm93bmFtZXMoc2FtcGxlLm1ldGEpIDwtIHNhbXBsZS5tZXRhJFNhbXBsZQojIHNpbTEubW9kZWwgPC0gbW9kZWwubWF0cml4KH4gMCArIENvbmRpdGlvbiwgZGF0YT1zYW1wbGUubWV0YSkKc2ltMS5tb2RlbCA8LSBtb2RlbC5tYXRyaXgofiBDb25kaXRpb24sIGRhdGE9c2FtcGxlLm1ldGEpCmhlYWQoc2ltMS5tb2RlbCkKYGBgCgpJIGhhdmUgYSBtb2RlbCBtYXRyaXggYW5kIGNvdW50cyBtYXRyaXggLSBsZXQncyB0ZXN0IGVkZ2VSIG9uIHRoZXNlLgoKYGBge3J9CnNpbTEuZGdlIDwtIERHRUxpc3Qoc2ltMS5jb3VudHNbLCByb3duYW1lcyhzaW0xLm1vZGVsKV0sIGxpYi5zaXplPWxvZyhjb2xTdW1zKHNpbTEuY291bnRzWywgcm93bmFtZXMoc2ltMS5tb2RlbCldKSkpCnNpbTEuZGdlIDwtIGVzdGltYXRlRGlzcChzaW0xLmRnZSwgc2ltMS5tb2RlbCkKc2ltMS5maXQgPC0gZ2xtUUxGaXQoc2ltMS5kZ2UsIHNpbTEubW9kZWwsIHJvYnVzdD1UUlVFKQojIHNpbTEuY29udHJhc3QgPC0gbWFrZUNvbnRyYXN0cyhDb25kaXRpb25BIC0gQ29uZGl0aW9uQiwgbGV2ZWxzPXNpbTEubW9kZWwpCiMgc2ltMS5yZXMgPC0gZ2xtUUxGVGVzdChzaW0xLmZpdCwgY29udHJhc3Q9c2ltMS5jb250cmFzdCkKCnNpbTEucmVzIDwtIGFzLmRhdGEuZnJhbWUodG9wVGFncyhnbG1RTEZUZXN0KHNpbTEuZml0LCBjb2VmPTIpLCBzb3J0LmJ5PSdub25lJywgbj1JbmYpKQpzaW0xLnJlcyRTaWcgPC0gYXMuZmFjdG9yKGFzLm51bWVyaWMoc2ltMS5yZXMkRkRSIDw9IDAuMDEpKQpzaW0xLnJlcyROZWlnaGJvdXJob29kIDwtIGFzLm51bWVyaWMocm93bmFtZXMoc2ltMS5yZXMpKQoKIyBjb250cm9sIHRoZSBzcGF0aWFsIEZEUgpxdmFscyA8LSBzaW0xLnJlcyRQVmFsdWUKaXMuc2lnIDwtIHF2YWxzIDw9IDAuMDEKc3VtbWFyeShpcy5zaWcpCmBgYAoKVGhpcyBpbmRpY2F0ZXMgdGhhdCAxMjMgb2YgdGhlIG5laWdoYm91ciBob29kcyBhcmUgc2lnbmlmaWNhbnQgYmVmb3JlIHNwYXRpYWwgRkRSIGNvcnJlY3Rpb24uIFRoZSBpbXBsZW1lbnRhdGlvbiBpbiBgQ3lkYXJgIHJlbGllcyBvbiBoYXZpbmcgdGhlIAptZWRpYW4gbWFya2VyIGludGVuc2l0aWVzIGZvciBlYWNoIGh5cGVyc3BoZXJlLiBXaGF0IGlzIHRoZSBlcXVpdmFsZW50IHBvc2l0aW9uYWwgaW5mb3JtYXRpb24gaGVyZT8gV2hhdCBhYm91dCBkcmF3aW5nIGEgbmV3IGdyYXBoIGZvciBlYWNoIApuZWlnaGJvcmhvb2QgdXNpbmcgdGhlIGBpZ3JhcGg6OmluZHVjZWRfc3ViZ3JhcGhgIGZ1bmN0aW9uLCB0aGVuIGNhbGN1bGF0ZSB0aGUgY29ubmVjdGl2aXR5IG9mIHRoaXMgbmV3IHN1Yi1ncmFwaCBhcyBhIG1lYXN1cmUgb2YgdGhlIApuZWlnaGJvcmhvb2QgZGVuc2l0eT8KCmBgYHtyfQojIHRoaXMgY3JlYXRlcyBhIHN1Yi1ncmFwaCBmb3IgZWFjaCBvZiB0aGUgcmFuZG9tIHZlcnRpY2VzCnRlc3Quc3ViZ3JhcGhzIDwtIGxhcHBseSgxOmxlbmd0aChyYW5kb20udmVydGljZXMpLAogICAgICAgICAgICAgICAgICAgICAgICAgRlVOPWZ1bmN0aW9uKFgpIGluZHVjZWRfc3ViZ3JhcGgoc2ltMS5rbm4sIHZlcnRleC5saXN0W1tYXV0pKQoKIyBub3cgbG9vcCBvdmVyIHRoZXNlIHN1Yi1ncmFwaHMgdG8gY2FsY3VsYXRlIHRoZSBjb25uZWN0aXZpdHkgLSB0aGlzIHNlZW1zIGEgbGl0dGxlIHNsb3cuLi4KdGVzdC5jb25uZWN0IDwtIGxhcHBseSh0ZXN0LnN1YmdyYXBocywgRlVOPWZ1bmN0aW9uKEVHKSB2ZXJ0ZXhfY29ubmVjdGl2aXR5KEVHKSkKYGBgCgpWZXJ0ZXggY29ubmVjdGl2aXR5IGlzIHNsb3cgdG8gY29tcHV0ZSAtIGVkZ2UgY29ubmVjdGl2aXR5IG1pZ2h0IGJlIGZhc3RlciBhbmQgaGFzIHRoZSB1c2VmdWwgcHJvcGVydHkgdGhhdDoKCiQkayhHKSBcbGVxIFxsYW1iZGEoRykgJCQKVGhhdCBpcywgdGhlIHZlcnRleCBjb25uZWN0aXZpdHkgJGsoRykkIGlzICRcbGVxJCB0aGFuIHRoZSBlZGdlIGNvbm5lY3Rpdml0eSAkXGxhbWJkYShHKSQuIEluIHRoaXMgaW5zdGFuY2UgdGhlIGVkZ2UtY29ubmVjdGl2aXR5IGlzIGFuIAp1cHBlci1ib3VuZCBvbiB0aGUgbmVpZ2hib3Job29kIGNvbm5lY3Rpdml0eSwgYW5kIHRodXMgYW4gdXBwZXIgYm91bmQgb24gdGhlIGRlbnNpdHksIGxlYWRpbmcgdG8gYSBzbGlnaHRseSBsb3dlciB3ZWlnaHRpbmcgZm9yIHRoZSBzcGF0aWFsIEZEUiAKYWRqdXN0bWVudC4KCmBgYHtyfQojIG5vdyBsb29wIG92ZXIgdGhlc2Ugc3ViLWdyYXBocyB0byBjYWxjdWxhdGUgdGhlIGNvbm5lY3Rpdml0eSAtIHRoaXMgc2VlbXMgYSBsaXR0bGUgc2xvdy4uLgplZGdlLmNvbm5lY3QgPC0gbGFwcGx5KHRlc3Quc3ViZ3JhcGhzLCBGVU49ZnVuY3Rpb24oRUcpIGVkZ2VfY29ubmVjdGl2aXR5KEVHKSkKYGBgCgpUaGUgZWRnZS1jb25uZWN0aXZpdHkgdGFrZXMgfjEwJSBvZiB0aGUgdGltZS4gSG93IGRvIHRoZXkgY29tcGFyZT8KCmBgYHtyfQpwbG90KHVubGlzdCh0ZXN0LmNvbm5lY3QpLCB1bmxpc3QoZWRnZS5jb25uZWN0KSwgbWFpbj0iR3JhcGgtY29ubmVjdGl2aXR5IG1lYXN1cmVzIiwgeGxhYj0iVmVydGV4LWNvbm5lY3Rpdml0eSIsIHlsYWI9IkVkZ2UtY29ubmVjdGl2aXR5IikKYGBgCgpGb3IgdGhlIG1vc3QgcGFydCB0aGUgdmVydGV4LWNvbm5lY3Rpdml0eSBhbmQgZWRnZS1jb25uZWN0aXZpdHkgZm9yIG5laWdoYm9yaG9vZHMgYXJlIGluIHByZXR0eSBnb29kIGFncmVlbWVudC4gSSdsbCBtYXliZSBpbmNsdWRlIHRoZSBvcHRpb24gCnRvIHVzZSBlaXRoZXIgYXMgYSBmdW5jdGlvbiBwYXJhbWV0ZXIuCgpgYGB7cn0KZ3JhcGhfc3BhdGlhbEZEUiA8LSBmdW5jdGlvbihuZWlnaGJvcmhvb2RzLCBncmFwaCwgcHZhbHVlcywgY29ubmVjdGl2aXR5PSd2ZXJ0ZXgnKXsKICAjIGlucHV0IGEgc2V0IG9mIG5laWdoYm9yaG9vZHMgYXMgYSBsaXN0IG9mIGdyYXBoIHZlcnRpY2VzCiAgIyB0aGUgaW5wdXQgZ3JhcGggYW5kIHRoZSB1bmFkanVzdGVkIEdMTSBwLXZhbHVlcwogICMnIG5laWdoYm9yaG9vZHM6IGxpc3Qgb2YgdmVydGljZXMgYW5kIHRoZWlyIHJlc3BlY3RpdmUgbmVpZ2hib3Job29kcwogICMnIGdyYXBoOiBpbnB1dCBrTk4gZ3JhcGgKICAjJyBwdmFsdWVzOiBhIHZlY3RvciBvZiBwdmFsdWVzIGluIHRoZSBzYW1lIG9yZGVyIGFzIHRoZSBuZWlnaGJvcmhvb2QgaW5kaWNlcwogICMnIGNvbm5lY3Rpdml0eTogY2hhcmFjdGVyIC0gZWRnZSBvciB2ZXJ0ZXggdG8gY2FsY3VsYXRlIG5laWdoYm9yaG9vZCBjb25uZWN0aXZpdHkKCiAgIyBEaXNjYXJkaW5nIE5BIHB2YWx1ZXMuCiAgaGFzcHZhbCA8LSAhaXMubmEocHZhbHVlcykKICBpZiAoIWFsbChoYXNwdmFsKSkgewogICAgICBjb29yZHMgPC0gY29vcmRzW2hhc3B2YWwsICwgZHJvcD1GQUxTRV0KICAgICAgcHZhbHVlcyA8LSBwdmFsdWVzW2hhc3B2YWxdCiAgfQogICAgCiAgIyBkZWZpbmUgdGhlIHN1YmdyYXBoIGZvciBlYWNoIG5laWdoYm9yaG9vZCB0aGVuIGNhbGN1bGF0ZSB0aGUgdmVydGV4IGNvbm5lY3Rpdml0eSBmb3IgZWFjaAogICMgdGhpcyBsYXR0ZXIgY29tcHV0YXRpb24gaXMgcXVpdGUgc2xvdyAtIGNhbiBpdCBiZSBzcGVkIHVwPwogIHN1YmdyYXBocyA8LSBsYXBwbHkoMTpsZW5ndGgobmVpZ2hib3Job29kc1toYXNwdmFsXSksCiAgICAgICAgICAgICAgICAgICAgICAgICBGVU49ZnVuY3Rpb24oWCkgaW5kdWNlZF9zdWJncmFwaChncmFwaCwgbmVpZ2hib3Job29kc1toYXNwdmFsXVtbWF1dKSkKCiAgIyBub3cgbG9vcCBvdmVyIHRoZXNlIHN1Yi1ncmFwaHMgdG8gY2FsY3VsYXRlIHRoZSBjb25uZWN0aXZpdHkgLSB0aGlzIHNlZW1zIGEgbGl0dGxlIHNsb3cuLi4KICBpZihjb25uZWN0aXZpdHkgPT0gInZlcnRleCIpewogICAgY29ubmVjdCA8LSBsYXBwbHkoc3ViZ3JhcGhzLCBGVU49ZnVuY3Rpb24oRUcpIHZlcnRleF9jb25uZWN0aXZpdHkoRUcpKQogIH0gZWxzZSBpZihjb25uZWN0aXZpdHkgPT0gImVkZ2UiKXsKICAgIGNvbm5lY3QgPC0gbGFwcGx5KHN1YmdyYXBocywgRlVOPWZ1bmN0aW9uKEVHKSBlZGdlX2Nvbm5lY3Rpdml0eShFRykpCiAgfSBlbHNlewogICAgc3RvcCgiY29ubmVjdGl2aXR5IG9wdGlvbiBub3QgcmVjb2duaXNlZCAtIG11c3QgYmUgZWl0aGVyIGVkZ2Ugb3IgdmVydGV4IikKICB9CiAgCiAgIyB1c2UgMS9jb25uZWN0aXZpdHkgYXMgdGhlIHdlaWdodGluZyBmb3IgdGhlIHdlaWdodGVkIEJIIGFkanVzdG1lbnQgZnJvbSBDeWRhcgogIHcgPC0gMS91bmxpc3QoY29ubmVjdCkKICAjIHNldCBJbmYgdG8gMAogIHdbaXMuaW5maW5pdGUodyldIDwtIDAKICAKICAjIENvbXB1dGluZyBhIGRlbnNpdHktd2VpZ2h0ZWQgcS12YWx1ZS4KICBvIDwtIG9yZGVyKHB2YWx1ZXMpCiAgcHZhbHVlcyA8LSBwdmFsdWVzW29dCiAgdyA8LSB3W29dCgogIGFkanAgPC0gbnVtZXJpYyhsZW5ndGgobykpCiAgYWRqcFtvXSA8LSByZXYoY3VtbWluKHJldihzdW0odykqcHZhbHVlcy9jdW1zdW0odykpKSkKICBhZGpwIDwtIHBtaW4oYWRqcCwgMSkKCiAgaWYgKCFhbGwoaGFzcHZhbCkpIHsKICAgIHJlZnAgPC0gcmVwKE5BX3JlYWxfLCBsZW5ndGgoaGFzcHZhbCkpCiAgICByZWZwW2hhc3B2YWxdIDwtIGFkanAKICAgIGFkanAgPC0gcmVmcAogICAgfQogIHJldHVybihhZGpwKQp9CmBgYAoKCmBgYHtyfQpzdGFydC50aW1lIDwtIFN5cy50aW1lKCkKc2ltMS5zcGF0aWFsZmRyIDwtIGdyYXBoX3NwYXRpYWxGRFIobmVpZ2hib3Job29kcz12ZXJ0ZXgubGlzdCwgZ3JhcGg9c2ltMS5rbm4sIGNvbm5lY3Rpdml0eT0iZWRnZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHB2YWx1ZXM9c2ltMS5yZXNbb3JkZXIoc2ltMS5yZXMkTmVpZ2hib3VyaG9vZCksIF0kUFZhbHVlKQplbmQudGltZSA8LSBTeXMudGltZSgpCmNvbm5lY3QudGltZSA8LSBlbmQudGltZSAtIHN0YXJ0LnRpbWUKc2ltMS5yZXMkU3BhdGlhbEZEUltvcmRlcihzaW0xLnJlcyROZWlnaGJvdXJob29kKV0gPC0gc2ltMS5zcGF0aWFsZmRyCnF2YWxzIDwtIHNpbTEuc3BhdGlhbGZkcgppcy5zaWcgPC0gcXZhbHMgPD0gMC4wMQpzdW1tYXJ5KGlzLnNpZykKYGBgCgpIb3cgZG8gdGhlc2UgY29tcGFyZSB0byB0aGUgc3RhbmRhcmQgRkRSIGFkanVzdG1lbnQ/CgpgYGB7cn0KcGxvdCgtbG9nMTAoc2ltMS5yZXNbb3JkZXIoc2ltMS5yZXMkTmVpZ2hib3VyaG9vZCksIF0kRkRSKSwKICAgICAtbG9nMTAoc2ltMS5zcGF0aWFsZmRyKSwgbWFpbj0iRkRSIGNvbXBhcmlzb24iLCB4bGFiPSItbG9nMTAgRkRSIiwgeWxhYj0iLWxvZzEwIFNwYXRpYWwgRkRSIikKYGBgCgpJbiB0aGlzIGluc3RhbmNlIHRoZXJlIGlzIG5vdCBhIGh1Z2UgZGlmZmVyZW5jZS4gVGhhdCBzZWVtcyB0byB3b3JrIGZhaXJseSB3ZWxsLiBEb2VzIGl0IG1ha2Ugc2Vuc2UgdGhvdWdoPyBpLmUuIGFyZSB0aGUgY2hhbmdlcyBpbiB0aGUgZ3JhcGggaW4gdGhlIApleHBlY3RlZCByZWdpb25zPyBJJ2xsIGZvbGxvdyB0aGUgYEN5ZGFyYCB3b3JrZmxvdyBmb3IgdGhpcyBhbmQgcHJvamVjdCB0aGUgbmVpZ2hib3VyaG9vZHMgaW50byBhIHJlZHVjZWQgZGltZW5zaW9uYWwgc3BhY2UuCgpgYGB7cn0KbmVpZ2hib3Job29kX2V4cHJlc3Npb24gPC0gZnVuY3Rpb24obmVpZ2hib3Job29kcywgZGF0YS5zZXQpewogICMgSSdsbCBjYWxjdWxhdGUgdGhlIGF2ZXJhZ2UgdmFsdWUgb2YgZWFjaCBuZWlnaGJvcmhvb2QgZm9yIGVhY2ggb2YgdGhlIG4gZmVhdHVyZXMgaW4gdGhlIGRhdGEuc2V0CiAgbmVpZ2hib3VyLm1vZGVsIDwtIG1hdHJpeCgwTCwgbmNvbD1sZW5ndGgobmVpZ2hib3Job29kcyksIG5yb3c9bmNvbChkYXRhLnNldCkpCiAgIyBuZWlnaGJvdXIubW9kZWwgPC0gc2FwcGx5KDE6bGVuZ3RoKG5laWdoYm9yaG9vZHMpLCBGVU49ZnVuY3Rpb24oWCkgcHJpbnQobGVuZ3RoKG5laWdoYm91ci5tb2RlbFssIFhdKSkpCiAgZm9yKHggaW4gc2VxX2Fsb25nKDE6bGVuZ3RoKG5laWdoYm9yaG9vZHMpKSl7CiAgICBuZWlnaGJvdXIubW9kZWxbbmVpZ2hib3Job29kc1tbeF1dLCB4XSA8LSAxCiAgfQogIAogIG5laWdoLmV4cHJzIDwtIHQobmVpZ2hib3VyLm1vZGVsKSAlKiUgdChkYXRhLnNldCkKICBuZWlnaC5leHBycyA8LSB0KGFwcGx5KG5laWdoLmV4cHJzLCAyLCBGVU49ZnVuY3Rpb24oWFApIFhQL2NvbFN1bXMobmVpZ2hib3VyLm1vZGVsKSkpCgogIHJldHVybihuZWlnaC5leHBycykKfQpgYGAKCgpgYGB7cn0Kc2ltMS5uZWlnaGJvdXIuZXhwcnMgPC0gbmVpZ2hib3Job29kX2V4cHJlc3Npb24odmVydGV4Lmxpc3QsIHNpbTEuZ2V4KQpgYGAKCkVtYmVkIHRoZXNlIGh5cGVyc3BoZXJlcyB3aXRoIGEgUENBIGFuZCBVTUFQLgoKYGBge3J9CnNpbTEubmVpZ2hib3VyLnBjYSA8LSBwcmNvbXAoKHQoc2ltMS5uZWlnaGJvdXIuZXhwcnMpKSkKcGFpcnMoc2ltMS5uZWlnaGJvdXIucGNhJHhbLCBjKDE6NSldKQpgYGAKCmBgYHtyfQpzZXQuc2VlZCg0MikKbmVpZ2hib3VyaG9vZC51bWFwIDwtIHVtYXAoc2ltMS5uZWlnaGJvdXIucGNhJHhbLCBjKDE6MzApXSwKICAgICAgICAgICAgICAgICAgICAgbl9jb21wb25lbnRzPTIsCiAgICAgICAgICAgICAgICAgICAgIG5fbmVpZ2hib3JzPTIxLCBtZXRyaWM9J2V1Y2xpZGVhbicsCiAgICAgICAgICAgICAgICAgICAgIGluaXQ9J3JhbmRvbScsIG1pbl9kaXN0PTAuMSkKcGxvdChuZWlnaGJvdXJob29kLnVtYXAkbGF5b3V0LAogICAgIHhsYWI9IlVNQVAgMSIsIHlsYWI9IlVNQVAgMiIpCmBgYAoKV2UgY2FuIG92ZXJsYXkgdGhlIERBIHRlc3Rpbmcgb24gdGhlc2UgbmVpZ2hib3VyaG9vZHMuCgpgYGB7cn0KbmVpZ2hib3IuZGYgPC0gc2ltMS5yZXNbLCBjKCJsb2dGQyIsICJOZWlnaGJvdXJob29kIiwgIlNwYXRpYWxGRFIiKV0KbmVpZ2hib3IuZGYgPC0gZG8uY2FsbChjYmluZC5kYXRhLmZyYW1lLCBsaXN0KG5laWdoYm9yLmRmLCBhcy5kYXRhLmZyYW1lKG5laWdoYm91cmhvb2QudW1hcCRsYXlvdXQpKSkKY29sbmFtZXMobmVpZ2hib3IuZGYpIDwtIGMoImxvZ0ZDIiwgIk5laWdoYm91cmhvb2QiLCAiU3BhdGlhbEZEUiIsICJVTUFQMSIsICJVTUFQMiIpCm5laWdoYm9yLmRmJFNpZyA8LSBhcy5udW1lcmljKG5laWdoYm9yLmRmJFNwYXRpYWxGRFIgPD0gMC4wNSkKCmdncGxvdChuZWlnaGJvci5kZiwgYWVzKHg9VU1BUDEsIHk9VU1BUDIpKSArCiAgZ2VvbV9wb2ludChkYXRhPW5laWdoYm9yLmRmW25laWdoYm9yLmRmJFNpZyA9PSAwLCBdLAogICAgICAgICAgICAgY29sb3VyPSdncmV5ODAnLCBzaXplPTIpICsKICBnZW9tX3BvaW50KGRhdGE9bmVpZ2hib3IuZGZbbmVpZ2hib3IuZGYkU2lnID09IDEsIF0sCiAgICAgICAgICAgICBhZXMoY29sb3VyPWxvZ0ZDKSwgc2l6ZT00KSArCiAgdGhlbWVfY2xlYW4oKSArCiAgc2NhbGVfY29sb3VyX2dyYWRpZW50Mihsb3c9ImJsdWUiLCBtaWQ9ImdyZXk4MCIsIGhpZ2g9InJlZCIpCmBgYAoKSW4gZWFjaCBuZWlnaGJvdXJob29kLCB3aGF0IGlzIHRoZSBtb3N0IGNvbW1vbiBjb25kaXRpb24gb3IgYmxvY2sgb2YgY2VsbHM/CgpgYGB7cn0KbmVpZ2hib3VyLmxpc3QgPC0gbGlzdCgpCmZvcih4IGluIHNlcV9hbG9uZygxOmxlbmd0aCh2ZXJ0ZXgubGlzdCkpKXsKICB4LmRmIDwtIG1ldGEuZGZbbWV0YS5kZiRWZXJ0ZXggJWluJSB2ZXJ0ZXgubGlzdFtbeF1dLCBdCiAgeC5yZXAgPC0gbmFtZXModGFibGUoeC5kZiRSZXBsaWNhdGUpKVt3aGljaCh0YWJsZSh4LmRmJFJlcGxpY2F0ZSkgPT0gbWF4KHRhYmxlKHguZGYkUmVwbGljYXRlKSkpXQogIGlmKGxlbmd0aCh4LnJlcCkgPiAxKXsKICAgIHgucmVwIDwtIHNhbXBsZShzaXplPTEsIHgucmVwKQogIH0KICB4LmJsb2NrIDwtIG5hbWVzKHRhYmxlKHguZGYkQmxvY2spKVt3aGljaCh0YWJsZSh4LmRmJEJsb2NrKSA9PSBtYXgodGFibGUoeC5kZiRCbG9jaykpKV0KICAgIGlmKGxlbmd0aCh4LmJsb2NrKSA+IDEpewogICAgeC5ibG9jayA8LSBzYW1wbGUoc2l6ZT0xLCB4LmJsb2NrKQogIH0KICB4LmNvbmRpdGlvbiA8LSBuYW1lcyh0YWJsZSh4LmRmJENvbmRpdGlvbikpW3doaWNoKHRhYmxlKHguZGYkQ29uZGl0aW9uKSA9PSBtYXgodGFibGUoeC5kZiRDb25kaXRpb24pKSldCiAgICBpZihsZW5ndGgoeC5jb25kaXRpb24pID4gMSl7CiAgICB4LmNvbmRpdGlvbiA8LSBzYW1wbGUoc2l6ZT0xLCB4LmNvbmRpdGlvbikKICB9CiAgCiAgbmVpZ2hib3VyLmxpc3RbW3hdXSA8LSBkYXRhLmZyYW1lKCJSZXBsaWNhdGUiPXgucmVwLCAiQmxvY2siPXguYmxvY2ssICJDb25kaXRpb24iPXguY29uZGl0aW9uLCAiTmVpZ2hib3VyaG9vZCI9eCkKfQoKbmVpZ2hib3VyLm1ldGEgPC0gZG8uY2FsbChyYmluZC5kYXRhLmZyYW1lLCBuZWlnaGJvdXIubGlzdCkKbmVpZ2hib3VyLm1lcmdlIDwtIG1lcmdlKG5laWdoYm9yLmRmLCBuZWlnaGJvdXIubWV0YSwgYnk9J05laWdoYm91cmhvb2QnKQpuZWlnaGJvdXIubWVyZ2UkQmxvY2sgPC0gb3JkZXJlZChuZWlnaGJvdXIubWVyZ2UkQmxvY2ssCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscz1jKCJCMSIsICJCMiIsICJCMyIpKQpuZWlnaGJvdXIubWVyZ2UkRGlmZiA8LSBzaWduKG5laWdoYm91ci5tZXJnZSRsb2dGQykKbmVpZ2hib3VyLm1lcmdlJERpZmZbbmVpZ2hib3VyLm1lcmdlJFNpZyA9PSAwXSA8LSAwCmBgYAoKCmBgYHtyLCBmaWcud2lkdGg9OS43NSwgZmlnLmhlaWdodD00LjE1fQpnZ3Bsb3QobmVpZ2hib3VyLm1lcmdlLCBhZXMoeD1VTUFQMSwgeT1VTUFQMikpICsKICBnZW9tX3BvaW50KGRhdGE9bmVpZ2hib3VyLm1lcmdlWywgYygiVU1BUDEiLCAiVU1BUDIiKV0sCiAgICAgICAgICAgICBjb2xvdXI9J2dyZXk4MCcsIHNpemU9MSkgKwogIGdlb21fcG9pbnQoZGF0YT1uZWlnaGJvdXIubWVyZ2VbbmVpZ2hib3VyLm1lcmdlJFNpZyA9PSAxLCBdLAogICAgICAgICAgICAgYWVzKGNvbG91cj1sb2dGQyksIHNpemU9NCkgKwogIHRoZW1lX2NsZWFuKCkgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudDIobG93PSJibHVlIiwgbWlkPSJncmV5ODAiLCBoaWdoPSJyZWQiKSArCiAgZmFjZXRfd3JhcCh+QmxvY2spCmBgYAoKVGhpcyBkb2Vzbid0IG1ha2Ugc2Vuc2UgLSBCbG9jayAzIHNob3VsZG4ndCBoYXZlIGFueSBEQSBuZWlnaGJvdXJob29kcy4gSXMgdGhpcyBhIGNvbXBvc2l0aW9uYWwgZWZmZWN0IHdlJ3JlIHNlZWluZyBoZXJlPyBJdCdzIHN0cmFuZ2UgCnRoYXQgYSByYW5kb20gZmx1Y3R1YXRpb24gd291bGQgY2F1c2UgdGhpcyAtIGl0IG11c3QgYmUgaW5jcmVkaWJseSBzZW5zaXRpdmUuCgpgYGB7cn0KdGFibGUobmVpZ2hib3VyLm1lcmdlJEJsb2NrLCBuZWlnaGJvdXIubWVyZ2UkRGlmZikKYGBgCgpJcyB0aGlzIGR1ZSB0byBzb21lIHdlaXJkIGdsb2JhbCBzY2FsaW5nIGRpZmZlcmVuY2VzIG9yIGlzIHRoaXMgYSBjb21wb3NpdGlvbmFsIGVmZmVjdD8gSW4gYWRkaXRpb24gdG8gdGhlIGhpZ2ggZmFsc2UtcG9zaXRpdmUgcmF0ZSwgdGhlcmUgYXJlIAphbHNvIHNpZ24gZGlmZmVyZW5jZXMgd2hlcmUgdGhleSBzaG91bGQgYmUgY29uY29yZGFudCBfd2l0aGluXyBhIGJsb2NrIG9mIGRhdGEgcG9pbnRzLiBGb3IgZWFjaCBuZWlnaGJvdXJob29kLCBob3cgbXVjaCBkb2VzIGl0IG92ZXJsYXAgd2l0aCB0aGUgCm92ZXIgYmxvY2tzPyBJbnR1aXRpdmVseSBJIHdvdWxkIGhhdmUgZXhwZWN0ZWQgMCBhcyB0aGV5IGFyZSB3ZWxsIHNlcGFyYXRlZCwgaG93ZXZlciwgdGhpcyBtaWdodCBiZSBhIGNhdXNlIGZvciBhbGwgdGhlc2UgZmFsc2UgREEgbmVpZ2hib3VyaG9vZHMuCgpfX05CX186IFRoaXMgd2FzIGR1ZSB0byBkaWZmZXJlbnQgdmVydGljZXMgYmVpbmcgc2FtcGxlZCwgc28gdGhlIHJlc3VsdHMgd2VyZW4ndCBjb25jb3JkYW50IC0gc2V0IHRoZSBzYW1lIHNlZWQgTWlrZSEhISEKClRoZSBmYWxzZS1wb3NpdGl2ZSByYXRlIGlzIGEgbGl0dGxlIGhpZ2ggaGVyZSwgYnV0IGF0IGxlYXN0IHRoZSBzaWducyBhcmUgdGhlIGNvcnJlY3Qgd2F5IGFyb3VuZC4KCmBgYHtyfQpwbG90KHg9c2ltMS5yZXMkbG9nRkMsIHk9LWxvZzEwKHNpbTEucmVzJFNwYXRpYWxGRFIpLCB4bGFiPSJsb2cgRkMiLCB5bGFiPSJTcGF0aWFsIEZEUiIsCiAgICAgY29sPWlmZWxzZShzaW0xLnJlcyRTaWcgPT0gMSwgInJlZCIsICJibGFjayIpKQpgYGAKCkZvciBlYWNoIG5laWdoYm91cmhvb2QsIEkgbmVlZCB0byBjb3VudCB0aGUgbnVtYmVyIG9mIHBvaW50cyBpbiBlYWNoIGJsb2NrLCBhcyB3ZWxsIGFzIGNvbmRpdGlvbiBhbmQgcmVwbGljYXRlLgoKYGBge3IsIGZpZy5oZWlnaHQ9OC45NSwgZmlnLndpZHRoPTkuOTV9CmFsbC5zYW1wcyA8LSB1bmlxdWUocGFzdGUobWV0YS5kZiRCbG9jaywgbWV0YS5kZiRDb25kaXRpb24sIG1ldGEuZGYkUmVwbGljYXRlLCBzZXA9Il8iKSkKbWV0YS5kZiRBbGwuU2FtcGxlIDwtIHBhc3RlKG1ldGEuZGYkQmxvY2ssIG1ldGEuZGYkQ29uZGl0aW9uLCBtZXRhLmRmJFJlcGxpY2F0ZSwgc2VwPSJfIikKYWxsLmNvdW50Lm1hdHJpeCA8LSBtYXRyaXgoMEwsIG5jb2w9bGVuZ3RoKGFsbC5zYW1wcyksIG5yb3c9bGVuZ3RoKHZlcnRleC5saXN0KSkKY29sbmFtZXMoYWxsLmNvdW50Lm1hdHJpeCkgPC0gYWxsLnNhbXBzCiAgCmZvcih4IGluIHNlcV9hbG9uZygxOmxlbmd0aCh2ZXJ0ZXgubGlzdCkpKXsKICB2LnggPC0gdmVydGV4Lmxpc3RbW3hdXQogIGZvcihpIGluIHNlcV9hbG9uZygxOmxlbmd0aChhbGwuc2FtcHMpKSl7CiAgICBpLnMgPC0gYWxsLnNhbXBzW2ldCiAgICBpLnMudmVydGljZXMgPC0gaW50ZXJzZWN0KHYueCwgbWV0YS5kZlttZXRhLmRmJEFsbC5TYW1wbGUgPT0gaS5zLCBdJFZlcnRleCkKICAgIGFsbC5jb3VudC5tYXRyaXhbeCwgaV0gPC0gbGVuZ3RoKGkucy52ZXJ0aWNlcykKICB9Cn0KCmFsbC5jb3VudC5tZWx0IDwtIG1lbHQoYWxsLmNvdW50Lm1hdHJpeCkKYWxsLmNvdW50Lm1lbHQkVmFyMiA8LSBhcy5jaGFyYWN0ZXIoYWxsLmNvdW50Lm1lbHQkVmFyMikKYWxsLmNvdW50Lm1lbHQkQmxvY2sgPC0gdW5saXN0KGxhcHBseShzdHJzcGxpdChhbGwuY291bnQubWVsdCRWYXIyLCBzcGxpdD0iXyIsIGZpeGVkPVRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZVTj1mdW5jdGlvbihYUCkgcGFzdGUwKFhQWzFdKSkpCmFsbC5jb3VudC5tZWx0JENvbmRpdGlvbiA8LSB1bmxpc3QobGFwcGx5KHN0cnNwbGl0KGFsbC5jb3VudC5tZWx0JFZhcjIsIHNwbGl0PSJfIiwgZml4ZWQ9VFJVRSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOPWZ1bmN0aW9uKFhQKSBwYXN0ZTAoWFBbMl0pKSkKYWxsLmNvdW50Lm1lbHQkUmVwbGljYXRlIDwtIHVubGlzdChsYXBwbHkoc3Ryc3BsaXQoYWxsLmNvdW50Lm1lbHQkVmFyMiwgc3BsaXQ9Il8iLCBmaXhlZD1UUlVFKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGVU49ZnVuY3Rpb24oWFApIHBhc3RlMChYUFszXSkpKQoKZ2dwbG90KGFsbC5jb3VudC5tZWx0LCBhZXMoeD1CbG9jaywgeT12YWx1ZSwgZmlsbD1Db25kaXRpb24pKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHRoZW1lX2NsZWFuKCkgKwogIGZhY2V0X3dyYXAoflZhcjEsIHNjYWxlcz0iZnJlZV95IikKYGBgCgpFYWNoIHBhbmVsIGlzIGEgbmVpZ2hib3VyaG9vZCwgdGhlIG51bWJlcnMgdGhlIGNvdW50cyBvZiBkYXRhIHBvaW50cyBpbiBlYWNoIHRoYXQgY29tIGZyb20gZWl0aGVyIGNvbmRpdGlvbiwgYW5kIGluIHRoZSBibG9jayBvZiBwb2ludHMuIFRoZXNlIAphcmUgbm93IGNvbmNvcmRhbnQgd2l0aCB0aGUgREEgcmVzdWx0cywgZXhjZXB0IG5laWdoYm91cmhvb2RzIDcgYW5kIDI2IC0gd2hpY2ggbG9vayBsaWtlIGEgY29tYmluYXRpb24gb2Ygc2FtcGxpbmcgdmFyaWFuY2UsIHdoaWNoIHRoZW4gCmNyZWF0ZXMgYSBjb21wb3NpdGlvbmFsIGVmZmVjdC4gRWl0aGVyIGEgZmlsdGVyIG9uIGxvdy1jb3VudCBuZWlnaGJvdXJob29kcyBvciBvbiB0aGUgbG9nIGZvbGQgY2hhbmdlcyB3b3VsZCBhbWVsaW9yYXRlIHRoaXMuCgpfX05CX186IEVtbWEgc3VnZ2VzdGVkIHVzaW5nIHRoZSB3aXRoaW4gbmVpZ2hib3VyaG9vZCBkaXN0YW5jZXMgaW5zdGVhZCBvZiBjb25uZWN0aXZpdHkgYXMgYSBtZWFzdXJlIG9mIGRlbnNpdHkuIFRoaXMgd291bGQgcmVxdWlyZSBtYWtpbmcgb3VyIApvd24gS05OLWdyYXBoIGJ1aWxkaW5nIGZ1bmN0aW9uIHRoYXQgcmV0YWlucyB0aGUgZGlzdGFuY2VzIGJldHdlZW4gYWxsIHBhaXJzIG9mIHZlcnRpY2VzLiBJbiB0cnV0aCwgdGhpcyBjb3VsZCBqdXN0IGJlIGFkZGVkIGFzIHRoZSBlZGdlIHdlaWdodHMgCnRvIHRoZSBLTk4tZ3JhcGgsIHdoaWNoIGFyZSB1bHRpbWF0ZWx5IGlnbm9yZWQgZm9yIHRoZSBwdXJwb3NlIG9mIGdyYXBoIGJ1aWxkaW5nLCBpLmUuIGFsbCBlZGdlcyA9IDEuCgojIyMgVXNpbmcgZGlzdGFuY2UgYmV0d2VlbiB2ZXJ0aWNlcyBmb3Igc3BhdGlhbCBGRFIgY29ycmVjdGlvbi4KCldlIGhhdmUgdGhlIFBDIGNvb3JkaW5hdGVzIHRoYXQgd2VyZSB1c2VkIGluIHRoZSBvcmlnaW5hbCBLTk4gZ3JhcGggYnVpbGRpbmcsIHNvIHN0cmljdGx5IHdlIGp1c3QgbmVlZCB0aGlzIHdpdGggdGhlIG5laWdoYm91cmhvb2QgbGlzdCB0byAKY2FsY3VsYXRlIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2VzLiBGb3IgdGhlIG5laWdoYm91cmhvb2QsIGRvIHdlIHRha2UgdGhlIGF2ZXJhZ2Ugb2ZmLWRpYWdvbmFsIGRpc3RhbmNlIGFzIHRoZSBkZW5zaXR5PwoKYGBge3J9Cm5laWdoYm91ci5kaXN0Lmxpc3QgPC0gbGlzdCgpCmZvcih4IGluIHNlcV9hbG9uZygxOmxlbmd0aCh2ZXJ0ZXgubGlzdCkpKXsKICB4LnZlcnRzIDwtIHZlcnRleC5saXN0W1t4XV0KICB4LnBjcyA8LSBzaW0xLnBjYSR4W3gudmVydHMsIGMoMTozMCldCiAgeC5ldWNsaWQgPC0gYXMubWF0cml4KGRpc3QoeC5wY3MpKQogIHguZGlzdGRlbnMgPC0gMS9tZWFuKHguZXVjbGlkW2xvd2VyLnRyaSh4LmV1Y2xpZCwgZGlhZz1GQUxTRSldKQogIG5laWdoYm91ci5kaXN0Lmxpc3RbW3hdXSA8LSB4LmRpc3RkZW5zCn0KCmhpc3QodW5saXN0KG5laWdoYm91ci5kaXN0Lmxpc3QpLCAxMDAsIHhsYWI9IjEvbWVhbiBFdWNsaWRlYW4gZGlzdGFuY2UiKQpgYGAKClRoaXMgbG9va3MgYSBsaXR0bGUgbGlrZSB0aGUgMyBibG9ja3Mgb2YgcG9pbnRzLi4uIExldCdzIHRyeSB0aGlzIGZvciB3ZWlnaHRpbmcgdGhlIHNwYXRpYWwgRkRSLgoKYGBge3J9CmdyYXBoX3NwYXRpYWxGRFIgPC0gZnVuY3Rpb24obmVpZ2hib3Job29kcywgZ3JhcGgsIHB2YWx1ZXMsIGNvbm5lY3Rpdml0eT0ndmVydGV4JywgcGNhPU5VTEwpewogICMgaW5wdXQgYSBzZXQgb2YgbmVpZ2hib3Job29kcyBhcyBhIGxpc3Qgb2YgZ3JhcGggdmVydGljZXMKICAjIHRoZSBpbnB1dCBncmFwaCBhbmQgdGhlIHVuYWRqdXN0ZWQgR0xNIHAtdmFsdWVzCiAgIycgbmVpZ2hib3Job29kczogbGlzdCBvZiB2ZXJ0aWNlcyBhbmQgdGhlaXIgcmVzcGVjdGl2ZSBuZWlnaGJvcmhvb2RzCiAgIycgZ3JhcGg6IGlucHV0IGtOTiBncmFwaAogICMnIHB2YWx1ZXM6IGEgdmVjdG9yIG9mIHB2YWx1ZXMgaW4gdGhlIHNhbWUgb3JkZXIgYXMgdGhlIG5laWdoYm9yaG9vZCBpbmRpY2VzCiAgIycgY29ubmVjdGl2aXR5OiBjaGFyYWN0ZXIgLSBlZGdlIG9yIHZlcnRleCB0byBjYWxjdWxhdGUgbmVpZ2hib3Job29kIGNvbm5lY3Rpdml0eSBvciBkaXN0YW5jZSB0byB1c2UgYXZlcmFnZSBFdWNsaWRlYW4gZGlzdGFuY2UKICAjJyBwY2E6IG1hdHJpeCBvZiBQQ3MgdG8gY2FsY3VsYXRlIEV1Y2xpZGVhbiBkaXN0YW5jZXMsIG9ubHkgcmVxdWlyZWQgd2hlbiBjb25uZWN0aXZpdHkgPT0gZGlzdGFuY2UKCiAgIyBEaXNjYXJkaW5nIE5BIHB2YWx1ZXMuCiAgaGFzcHZhbCA8LSAhaXMubmEocHZhbHVlcykKICBpZiAoIWFsbChoYXNwdmFsKSkgewogICAgICBjb29yZHMgPC0gY29vcmRzW2hhc3B2YWwsICwgZHJvcD1GQUxTRV0KICAgICAgcHZhbHVlcyA8LSBwdmFsdWVzW2hhc3B2YWxdCiAgfQogICAgCiAgIyBkZWZpbmUgdGhlIHN1YmdyYXBoIGZvciBlYWNoIG5laWdoYm9yaG9vZCB0aGVuIGNhbGN1bGF0ZSB0aGUgdmVydGV4IGNvbm5lY3Rpdml0eSBmb3IgZWFjaAogICMgdGhpcyBsYXR0ZXIgY29tcHV0YXRpb24gaXMgcXVpdGUgc2xvdyAtIGNhbiBpdCBiZSBzcGVkIHVwPwogIHN1YmdyYXBocyA8LSBsYXBwbHkoMTpsZW5ndGgobmVpZ2hib3Job29kc1toYXNwdmFsXSksCiAgICAgICAgICAgICAgICAgICAgICAgICBGVU49ZnVuY3Rpb24oWCkgaW5kdWNlZF9zdWJncmFwaChncmFwaCwgbmVpZ2hib3Job29kc1toYXNwdmFsXVtbWF1dKSkKCiAgIyBub3cgbG9vcCBvdmVyIHRoZXNlIHN1Yi1ncmFwaHMgdG8gY2FsY3VsYXRlIHRoZSBjb25uZWN0aXZpdHkgLSB0aGlzIHNlZW1zIGEgbGl0dGxlIHNsb3cuLi4KICBpZihjb25uZWN0aXZpdHkgPT0gInZlcnRleCIpewogICAgdC5jb25uZWN0IDwtIGxhcHBseShzdWJncmFwaHMsIEZVTj1mdW5jdGlvbihFRykgdmVydGV4X2Nvbm5lY3Rpdml0eShFRykpCiAgfSBlbHNlIGlmKGNvbm5lY3Rpdml0eSA9PSAiZWRnZSIpewogICAgdC5jb25uZWN0IDwtIGxhcHBseShzdWJncmFwaHMsIEZVTj1mdW5jdGlvbihFRykgZWRnZV9jb25uZWN0aXZpdHkoRUcpKQogIH0gZWxzZSBpZihjb25uZWN0aXZpdHkgPT0gImRpc3RhbmNlIil7CiAgICBpZighaXMubnVsbChwY2EpKXsKICAgICAgdC5jb25uZWN0IDwtIGxhcHBseSgxOmxlbmd0aChuZWlnaGJvcmhvb2RzW2hhc3B2YWxdKSwKICAgICAgICAgICAgICAgICAgICAgICAgRlVOPWZ1bmN0aW9uKFBHKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgeC5wY3MgPC0gcGNhW25laWdoYm9yaG9vZHNbaGFzcHZhbF1bW1BHXV0sIF0KICAgICAgICAgICAgICAgICAgICAgICAgICB4LmV1Y2xpZCA8LSBhcy5tYXRyaXgoZGlzdCh4LnBjcykpCiAgICAgICAgICAgICAgICAgICAgICAgICAgeC5kaXN0ZGVucyA8LSAxL21lYW4oeC5ldWNsaWRbbG93ZXIudHJpKHguZXVjbGlkLCBkaWFnPUZBTFNFKV0pCiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybih4LmRpc3RkZW5zKX0pCiAgICB9IGVsc2V7CiAgICAgIHN0b3AoIkEgbWF0cml4IG9mIFBDcyBpcyByZXF1aXJlZCB0byBjYWxjdWxhdGUgZGlzdGFuY2VzIikgIAogICAgfQogIH1lbHNlewogICAgc3RvcCgiY29ubmVjdGl2aXR5IG9wdGlvbiBub3QgcmVjb2duaXNlZCAtIG11c3QgYmUgZWl0aGVyIGVkZ2UsIHZlcnRleCBvciBkaXN0YW5jZSIpCiAgfQogIAogICMgdXNlIDEvY29ubmVjdGl2aXR5IGFzIHRoZSB3ZWlnaHRpbmcgZm9yIHRoZSB3ZWlnaHRlZCBCSCBhZGp1c3RtZW50IGZyb20gQ3lkYXIKICB3IDwtIDEvdW5saXN0KHQuY29ubmVjdCkKICB3W2lzLmluZmluaXRlKHcpXSA8LSAwCiAgCiAgIyBDb21wdXRpbmcgYSBkZW5zaXR5LXdlaWdodGVkIHEtdmFsdWUuCiAgbyA8LSBvcmRlcihwdmFsdWVzKQogIHB2YWx1ZXMgPC0gcHZhbHVlc1tvXQogIHcgPC0gd1tvXQoKICBhZGpwIDwtIG51bWVyaWMobGVuZ3RoKG8pKQogIGFkanBbb10gPC0gcmV2KGN1bW1pbihyZXYoc3VtKHcpKnB2YWx1ZXMvY3Vtc3VtKHcpKSkpCiAgYWRqcCA8LSBwbWluKGFkanAsIDEpCgogIGlmICghYWxsKGhhc3B2YWwpKSB7CiAgICByZWZwIDwtIHJlcChOQV9yZWFsXywgbGVuZ3RoKGhhc3B2YWwpKQogICAgcmVmcFtoYXNwdmFsXSA8LSBhZGpwCiAgICBhZGpwIDwtIHJlZnAKICAgIH0KICByZXR1cm4oYWRqcCkKfQpgYGAKCgpgYGB7cn0Kc3RhcnQudGltZSA8LSBTeXMudGltZSgpCnNpbTEuc3BhdGlhbGZkciA8LSBncmFwaF9zcGF0aWFsRkRSKG5laWdoYm9yaG9vZHM9dmVydGV4Lmxpc3QsIGdyYXBoPXNpbTEua25uLCBjb25uZWN0aXZpdHk9ImRpc3RhbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGNhPXNpbTEucGNhJHhbLCBjKDE6MzApXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHZhbHVlcz1zaW0xLnJlc1tvcmRlcihzaW0xLnJlcyROZWlnaGJvdXJob29kKSwgXSRQVmFsdWUpCmVuZC50aW1lIDwtIFN5cy50aW1lKCkKZGlzdGFuY2UudGltZSA8LSBlbmQudGltZSAtIHN0YXJ0LnRpbWUKc2ltMS5yZXMkU3BhdGlhbEZEUi5EaXN0W29yZGVyKHNpbTEucmVzJE5laWdoYm91cmhvb2QpXSA8LSBzaW0xLnNwYXRpYWxmZHIKcXZhbHMgPC0gc2ltMS5zcGF0aWFsZmRyCmlzLnNpZyA8LSBxdmFscyA8PSAwLjAxCnN1bW1hcnkoaXMuc2lnKQpgYGAKCkhvdyBkb2VzIHRoaXMgY29tcGFyZSB3aXRoIHVzaW5nIGNvbm5lY3Rpdml0eSBmb3IgdGhlIHNwYXRpYWwgRkRSIGNvcnJlY3Rpb24/CgpgYGB7cn0Kc2luayhmaWxlPSIvZGV2L251bGwiKQpwZGYoIn4vRHJvcGJveC9NaWxvL3NpbXVsYXRpb25zL0Nvbm5lY3Rpdml0eV92c19kaXN0YW5jZV9TcGF0aWFsRkRSLnBkZiIsCiAgICBoZWlnaD0zLjE1LCB3aWR0aD00LjI1LCB1c2VEaW5nYmF0cz1GQUxTRSkKcGxvdCgtbG9nMTAoc2ltMS5yZXMkU3BhdGlhbEZEUi5EaXN0KSwgLWxvZzEwKHNpbTEucmVzJFNwYXRpYWxGRFIpLCB4bGFiPSItbG9nMTAgZGlzdGFuY2UgU3BhdGlhbCBGRFIiLAogICAgIHlsYWI9Ii1sb2cxMCBjb25uZWN0aXZpdHkgU3BhdGlhbCBGRFIiKQpkZXYub2ZmKCkKc2luayhmaWxlPU5VTEwpCgpwbG90KC1sb2cxMChzaW0xLnJlcyRTcGF0aWFsRkRSLkRpc3QpLCAtbG9nMTAoc2ltMS5yZXMkU3BhdGlhbEZEUiksIHhsYWI9Ii1sb2cxMCBkaXN0YW5jZSBTcGF0aWFsIEZEUiIsCiAgICAgeWxhYj0iLWxvZzEwIGNvbm5lY3Rpdml0eSBTcGF0aWFsIEZEUiIpCmBgYAoKQXNpZGUgZnJvbSB0aGUgc21hbGwgbnVtYmVyIG9mIGZhbHNlLXBvc2l0aXZlIHNhbXBsZXMsIHRoaXMgd29ya3MgZmFpcmx5IHdlbGwgb24gdGhlc2Ugc2ltdWxhdGVkIGRhdGEuIENhbiB3ZSBhbHNvIGdldCBzaW1pbGFyIHJlc3VsdHMgd2l0aCBhIApyZWFsLXdvcmxkIGRhdGE/IEZvciB0aGlzICdsbCB0ZXN0IHRoZSAxIGFuZCA1MiB3ZWVrIG9sZCBURUMgZnJvbSBvdXIgQWdlaW5nIHRoeW11cyBwYXBlciwgYXMgdGhlc2UgaGF2ZSB0aGUgYmlnZ2VzdCBkaWZmZXJlbmNlcyBiZXR3ZWVuIHRoZW0uCgojIyBBZ2VpbmcgVEVDCgpgYGB7cn0KIyByZWFkIGluIG5vcm1hbGlzZWQgZGF0YSwgc3Vic2V0IHRvIDEgYW5kIDUyIHdlZWsgb2xkIFRFQywgZG8gYSBQQ0EgYW5kIGNvbnN0cnVjdCBhIGtOTiBncmFwaC4KdGVjLm1ldGEgPC0gcmVhZC50YWJsZSgifi9Ecm9wYm94L0FnZWluZ0V4cGVyaW1lbnQvRnJvemVuL1RoeW11c01hcmtlcl90U05FX1BDQV9tZXRhLnRzdiIsCiAgICAgICAgICAgICAgICAgICAgICAgc2VwPSJcdCIsIGhlYWRlcj1UUlVFLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQojIGV4Y2x1ZGUgdGVjaG5pY2FsIGFydGlmYWN0IGNsdXN0ZXIKdGVjLm1ldGEgPC0gdGVjLm1ldGFbIXRlYy5tZXRhJFRGSURGLkNsdXN0ZXIgJWluJSBjKDQpLCBdCnRlYy5zdWIubWV0YSA8LSB0ZWMubWV0YVt0ZWMubWV0YSRBZ2UgJWluJSBjKCIxd2siLCAiNTJ3ayIpLCBdCgojIGFkZCB0aGUgbGFiZWwgYW5ub3RhdGlvbgp0ZWMuc3ViLm1ldGEkQ2x1c3RlciA8LSAiVW5rbm93biIKdGVjLnN1Yi5tZXRhJENsdXN0ZXJbdGVjLnN1Yi5tZXRhJFRGSURGLkNsdXN0ZXIgPT0gIjIiXSA8LSAiSW50ZXJ0eXBpY2FsIFRFQyIKdGVjLnN1Yi5tZXRhJENsdXN0ZXJbdGVjLnN1Yi5tZXRhJFRGSURGLkNsdXN0ZXIgPT0gIjkiXSA8LSAiUGVyaW5hdGFsIGNURUMiCnRlYy5zdWIubWV0YSRDbHVzdGVyW3RlYy5zdWIubWV0YSRURklERi5DbHVzdGVyID09ICIzIl0gPC0gIk1hdHVyZSBjVEVDIgp0ZWMuc3ViLm1ldGEkQ2x1c3Rlclt0ZWMuc3ViLm1ldGEkVEZJREYuQ2x1c3RlciA9PSAiNyJdIDwtICJNYXR1cmUgbVRFQyIKdGVjLnN1Yi5tZXRhJENsdXN0ZXJbdGVjLnN1Yi5tZXRhJFRGSURGLkNsdXN0ZXIgPT0gIjEiXSA8LSAiUG9zdC1BaXJlIG1URUMiCnRlYy5zdWIubWV0YSRDbHVzdGVyW3RlYy5zdWIubWV0YSRURklERi5DbHVzdGVyID09ICI1Il0gPC0gIlR1ZnQtbGlrZSBtVEVDIgp0ZWMuc3ViLm1ldGEkQ2x1c3Rlclt0ZWMuc3ViLm1ldGEkVEZJREYuQ2x1c3RlciA9PSAiNiJdIDwtICJQcm9saWZlcmF0aW5nIFRFQyIKdGVjLnN1Yi5tZXRhJENsdXN0ZXJbdGVjLnN1Yi5tZXRhJFRGSURGLkNsdXN0ZXIgPT0gIjgiXSA8LSAiblRFQyIKdGVjLnN1Yi5tZXRhJENsdXN0ZXJbdGVjLnN1Yi5tZXRhJFRGSURGLkNsdXN0ZXIgPT0gIjEwIl0gPC0gInNURUMiCgppbnRlci5jb2xzIDwtIGMoIiM5OTcwYWIiLCAiIzM1OTc4ZiIsICIjQjBjZGMxIiwgIiM3NjJhODMiLCAiIzAxNjY1ZSIsICIjZTdkNGU4IiwgIiNkZmMyN2QiLCAiIzhjNTEwYSIgLCIjYmY4MTJkIikKbmFtZXMoaW50ZXIuY29scykgPC0gYygiUG9zdC1BaXJlIG1URUMiLCAnSW50ZXJ0eXBpY2FsIFRFQycsICdNYXR1cmUgY1RFQycsICdUdWZ0LWxpa2UgbVRFQycsIAogICAgICAgICAgICAgICAgICAgICAgICdQcm9saWZlcmF0aW5nIFRFQycsICdNYXR1cmUgbVRFQycsICduVEVDJywgJ1BlcmluYXRhbCBjVEVDJywgJ3NURUMnKQpgYGAKCmBgYHtyfQp0ZWMuZ2V4IDwtIHJlYWQudGFibGUoIn4vRHJvcGJveC9BZ2VpbmdFeHBlcmltZW50L0Zyb3plbi9UaHltdXNRQ19TRm5vcm0udHN2Lmd6IiwKICAgICAgICAgICAgICAgICAgICAgIHNlcD0iXHQiLCBoZWFkZXI9VFJVRSwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkKdGVjLnN1Yi5nZXggPC0gdGVjLmdleFssIGNvbG5hbWVzKHRlYy5nZXgpICVpbiUgdGVjLnN1Yi5tZXRhJFNhbXBsZV0KCnRlYy5odmdzIDwtIHJlYWQudGFibGUoIn4vRHJvcGJveC9BZ2VpbmdFeHBlcmltZW50L0Zyb3plbi9UaHltdXNfSFZHLnRzdiIsCiAgICAgICAgICAgICAgICAgICAgICAgc2VwPSJcdCIsIGhlYWRlcj1UUlVFLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQpgYGAKCkknbGwgYnVpbGQgYSBrTk4tZ3JhcGggZnJvbSB0aGUgZmlyc3QgMzAgUENzIHByZXZpb3VzbHkgY29tcHV0ZWQgb24gYWxsIFRFQy4KCmBgYHtyfQpzZXQuc2VlZCg0MikKdGVjLmtubiA8LSBidWlsZEtOTkdyYXBoKHg9YXMubWF0cml4KHRlYy5zdWIubWV0YVssIHBhc3RlMCgiUEMiLCAxOjMwKV0pLCBrPTIxLCBkPU5BLCB0cmFuc3Bvc2VkPVRSVUUpCnRlYy5mci5sYXlvdXQgPC0gbGF5b3V0X3dpdGhfZnIodGVjLmtubikKcGxvdCh0ZWMua25uLCBsYXlvdXQ9dGVjLmZyLmxheW91dCwgdmVydGV4LmZyYW1lLmNvbG9yPSdza3libHVlJywgdmVydGV4LmNvbG9yPSdza3libHVlJywgdmVydGV4LmxhYmVsLmNvbG9yPSdibGFjaycsIAogICAgIHZlcnRleC5sYWJlbC5mYW1pbHk9J0hlbHZldGljYScsIGVkZ2UuY29sb3I9J2dyZXk2MCcsIHZlcnRleC5sYWJlbC5jZXg9MC45LAogICAgIHZlcnRleC5sYWJlbC5kaXN0PTEsIGVkZ2UuYXJyb3cuc2l6ZT0wLjIpCmBgYAoKVGhpcyBpcyBhIGZhaXJseSBkZW5zZWx5IGNvbm5lY3RlZCBuZXR3b3JrLCBob3cgZG9lcyB0aGUgVU1BUCBsb29rPwoKYGBge3J9CnNldC5zZWVkKDQyKQp0ZWMudW1hcCA8LSB1bWFwKGFzLm1hdHJpeCh0ZWMuc3ViLm1ldGFbLCBwYXN0ZTAoIlBDIiwgMTozMCldKSwKICAgICAgICAgICAgICAgICBuX2NvbXBvbmVudHM9MiwKICAgICAgICAgICAgICAgICBuX25laWdoYm9ycz0yMSwgbWV0cmljPSdldWNsaWRlYW4nLAogICAgICAgICAgICAgICAgIGluaXQ9J3JhbmRvbScsIG1pbl9kaXN0PTAuMikKdGVjLnVtYXAuZGYgPC0gYXMuZGF0YS5mcmFtZSh0ZWMudW1hcCRsYXlvdXQpCmNvbG5hbWVzKHRlYy51bWFwLmRmKSA8LSBjKCJVTUFQMSIsICJVTUFQMiIpCnRlYy51bWFwLmRmJFNhbXBsZSA8LSB0ZWMuc3ViLm1ldGEkU2FtcGxlCgp0ZWMudW1hcC5tZXJnZSA8LSBtZXJnZSh0ZWMudW1hcC5kZiwgdGVjLnN1Yi5tZXRhLCBieT0nU2FtcGxlJykKYGBgCgoKYGBge3IsIGZpZy53aWR0aD03Ljk1LCBmaWcuaGVpZ2h0PTQuMTV9CmdncGxvdCh0ZWMudW1hcC5tZXJnZSwgYWVzKHg9VU1BUDEsIHk9VU1BUDIpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyPUNsdXN0ZXIpKSArCiAgdGhlbWVfY2xlYW4oKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9aW50ZXIuY29scykgKwogIGZhY2V0X3dyYXAofkFnZSkgKwogIGd1aWRlcyhjb2xvdXI9Z3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcz1saXN0KHNpemU9MykpLAogICAgICAgICBzaGFwZT1ndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzPWxpc3Qoc2l6ZT0zKSkpCmBgYAoKCmBgYHtyfQojIHJhbmRvbWx5IHNlbGVjdCB2ZXJ0aWNlcyBpbiB0aGUgZ3JhcGgKbi5ob29kIDwtIDAuMDUKdGVjLnJhbmRvbS52ZXJ0aWNlcyA8LSBzYW1wbGUoVih0ZWMua25uKSwgc2l6ZT1mbG9vcihuLmhvb2QqbGVuZ3RoKFYodGVjLmtubikpKSkKIyBsb29wIG92ZXIgcmFuZG9tIHZlcnRpY2VzIGFuZCBjb3VudCB0aGUgbnVtYmVyIG9mIGNlbGxzIGluIGVhY2gKdGVjLnZlcnRleC5saXN0IDwtIHNhcHBseSgxOmxlbmd0aCh0ZWMucmFuZG9tLnZlcnRpY2VzKSwgRlVOPWZ1bmN0aW9uKFgpIG5laWdoYm9ycyh0ZWMua25uLCB2PXRlYy5yYW5kb20udmVydGljZXNbWF0pKQpoaXN0KHVubGlzdChsYXBwbHkodGVjLnZlcnRleC5saXN0LCBsZW5ndGgpKSwgMTAwLCBtYWluPSJIaXN0b2dyYW0gb2YgbmVpZ2hib3JzIiwgeGxhYj0iTmVpZ2hib3VyaG9vZCBzaXplIikKYGBgCgpUaGlzIGlzIHRoZSBoaXN0b2dyYW0gb2YgVEVDIG5laWdoYm91cmhvb2Qgc2l6ZXMuCgpgYGB7cn0KdGVjLnVtYXAubWVyZ2UkRXhwU2FtcCA8LSBwYXN0ZSh0ZWMudW1hcC5tZXJnZSRBZ2UsIHRlYy51bWFwLm1lcmdlJFNvcnREYXksIHNlcD0iXyIpCnRlYy51bWFwLm1lcmdlJFZlcnRleCA8LSBjKDE6bnJvdyh0ZWMudW1hcC5tZXJnZSkpCnRlYy5jb3VudHMgPC0gcXVhbnRfbmVpZ2hib3VyaG9vZChncmFwaD10ZWMua25uLCBtZXRhPXRlYy51bWFwLm1lcmdlLCBzYW1wbGUuY29sdW1uPSdFeHBTYW1wJywgc2FtcGxlLnZlcnRpY2VzPW4uaG9vZCkKYGBgCgoKYGBge3J9CnRlYy5yZXBzIDwtIHVubGlzdChsYXBwbHkoc3Ryc3BsaXQodW5pcXVlKHRlYy51bWFwLm1lcmdlJEV4cFNhbXApLCBzcGxpdD0iXyIpLCBGVU49ZnVuY3Rpb24oWCkgcGFzdGUwKFhbMl0pKSkKdGVjLmNvbmQgPC0gdW5saXN0KGxhcHBseShzdHJzcGxpdCh1bmlxdWUodGVjLnVtYXAubWVyZ2UkRXhwU2FtcCksIHNwbGl0PSJfIiksIEZVTj1mdW5jdGlvbihYKSBwYXN0ZTAoWFsxXSkpKQoKdGVjLnNhbXBsZS5tZXRhIDwtIGRhdGEuZnJhbWUoIkNvbmRpdGlvbiI9dGVjLmNvbmQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJSZXBsaWNhdGUiPXRlYy5yZXBzKQp0ZWMuc2FtcGxlLm1ldGEkU2FtcGxlIDwtIHBhc3RlKHRlYy5zYW1wbGUubWV0YSRDb25kaXRpb24sIHRlYy5zYW1wbGUubWV0YSRSZXBsaWNhdGUsIHNlcD0iXyIpCnJvd25hbWVzKHRlYy5zYW1wbGUubWV0YSkgPC0gdGVjLnNhbXBsZS5tZXRhJFNhbXBsZQoKdGVjLm1vZGVsIDwtIG1vZGVsLm1hdHJpeCh+IENvbmRpdGlvbiwgZGF0YT10ZWMuc2FtcGxlLm1ldGEpCmhlYWQodGVjLm1vZGVsKQpgYGAKCmBgYHtyfQp0ZWMuZGdlIDwtIERHRUxpc3QodGVjLmNvdW50c1ssIHJvd25hbWVzKHRlYy5tb2RlbCldLCBsaWIuc2l6ZT1sb2coY29sU3Vtcyh0ZWMuY291bnRzWywgcm93bmFtZXModGVjLm1vZGVsKV0pKSkKdGVjLmRnZSA8LSBlc3RpbWF0ZURpc3AodGVjLmRnZSwgdGVjLm1vZGVsKQp0ZWMuZml0IDwtIGdsbVFMRml0KHRlYy5kZ2UsIHRlYy5tb2RlbCwgcm9idXN0PVRSVUUpCiMgdGVjLmNvbnRyYXN0IDwtIG1ha2VDb250cmFzdHMoQ29uZGl0aW9uQSAtIENvbmRpdGlvbkIsIGxldmVscz10ZWMubW9kZWwpCiMgdGVjLnJlcyA8LSBnbG1RTEZUZXN0KHRlYy5maXQsIGNvbnRyYXN0PXRlYy5jb250cmFzdCkKCnRlYy5yZXMgPC0gYXMuZGF0YS5mcmFtZSh0b3BUYWdzKGdsbVFMRlRlc3QodGVjLmZpdCwgY29lZj0yKSwgc29ydC5ieT0nbm9uZScsIG49SW5mKSkKdGVjLnJlcyRTaWcgPC0gYXMuZmFjdG9yKGFzLm51bWVyaWModGVjLnJlcyRGRFIgPD0gMC4wMSkpCnRlYy5yZXMkTmVpZ2hib3VyaG9vZCA8LSBhcy5udW1lcmljKHJvd25hbWVzKHRlYy5yZXMpKQoKIyBjb250cm9sIHRoZSBzcGF0aWFsIEZEUgpxdmFscyA8LSB0ZWMucmVzJFBWYWx1ZQppcy5zaWcgPC0gcXZhbHMgPD0gMC4wMQpzdW1tYXJ5KGlzLnNpZykKYGBgCgpUaGVyZSBhcmUgMTggREEgbmVpZ2hib3VyaG9vZHMgLSBJIGV4cGVjdCB0aGVzZSBzaG91bGQgcmVmbGVjdCB0aGUgUGVyaW5hdGFsLCBJbnRlcnR5cGljYWwsIFByb2xpZmVyYXRpbmcgYW5kIHNURUMuIEknbGwgdXNlIHRoZSBkaXN0YW5jZS1iYXNlZCBhcHByb2FjaCB0byBjb3JyZWN0IGZvciAKdGhlIHNwYXRpYWwgRkRSLgoKYGBge3J9CnRlYy5zcGF0aWFsZmRyIDwtIGdyYXBoX3NwYXRpYWxGRFIobmVpZ2hib3Job29kcz10ZWMudmVydGV4Lmxpc3QsIGdyYXBoPXRlYy5rbm4sIGNvbm5lY3Rpdml0eT0iZGlzdGFuY2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBjYT1hcy5tYXRyaXgodGVjLnN1Yi5tZXRhWywgcGFzdGUwKCJQQyIsIDE6MzApXSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHZhbHVlcz10ZWMucmVzW29yZGVyKHRlYy5yZXMkTmVpZ2hib3VyaG9vZCksIF0kUFZhbHVlKQp0ZWMucmVzJFNwYXRpYWxGRFJbb3JkZXIodGVjLnJlcyROZWlnaGJvdXJob29kKV0gPC0gdGVjLnNwYXRpYWxmZHIKcXZhbHMgPC0gdGVjLnNwYXRpYWxmZHIKaXMuc2lnIDwtIHF2YWxzIDw9IDAuMDEKc3VtbWFyeShpcy5zaWcpCmBgYAoKSW50ZXJlc3RpbmcsIDMgbmVpZ2hib3VyaG9vZHMgYXJlIG5vIGxvbmdlciBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IGFmdGVyIHRoZSBzcGF0aWFsIEZEUiBjb3JyZWN0aW9uIC0gaG9wZWZ1bGx5IHRoZXNlIGFyZSBnZW51aW5lbHkgZmFsc2UtcG9zaXRpdmVzLgoKSW4gZWFjaCBuZWlnaGJvdXJob29kLCB3aGF0IGlzIHRoZSBtb3N0IGNvbW1vbiBjb25kaXRpb24gb3IgYmxvY2sgb2YgY2VsbHM/CgpgYGB7cn0KdGVjLm5laWdoYm91ci5leHBycyA8LSBuZWlnaGJvcmhvb2RfZXhwcmVzc2lvbih0ZWMudmVydGV4Lmxpc3QsIHRlYy5zdWIuZ2V4KQpgYGAKCkVtYmVkIHRoZXNlIGh5cGVyc3BoZXJlcyB3aXRoIGEgUENBIGFuZCBVTUFQLgoKYGBge3J9CnRlYy5uZWlnaGJvdXIucGNhIDwtIHByY29tcCgodCh0ZWMubmVpZ2hib3VyLmV4cHJzW3RlYy5odmdzJEhWRywgXSkpKQpwYWlycyh0ZWMubmVpZ2hib3VyLnBjYSR4WywgYygxOjUpXSkKYGBgCgpgYGB7cn0Kc2V0LnNlZWQoNDIpCm5laWdoYm91cmhvb2QudW1hcCA8LSB1bWFwKHRlYy5uZWlnaGJvdXIucGNhJHhbLCBjKDE6MzApXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jb21wb25lbnRzPTIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fbmVpZ2hib3JzPTIxLCBtZXRyaWM9J2V1Y2xpZGVhbicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGluaXQ9J3JhbmRvbScsIG1pbl9kaXN0PTAuMSkKYGBgCgpXZSBjYW4gb3ZlcmxheSB0aGUgREEgdGVzdGluZyBvbiB0aGVzZSBuZWlnaGJvdXJob29kcy4KCmBgYHtyfQp0ZWMubmVpZ2hib3IuZGYgPC0gdGVjLnJlc1ssIGMoImxvZ0ZDIiwgIk5laWdoYm91cmhvb2QiLCAiU3BhdGlhbEZEUiIpXQp0ZWMubmVpZ2hib3IuZGYgPC0gZG8uY2FsbChjYmluZC5kYXRhLmZyYW1lLCBsaXN0KHRlYy5uZWlnaGJvci5kZiwgYXMuZGF0YS5mcmFtZShuZWlnaGJvdXJob29kLnVtYXAkbGF5b3V0KSkpCmNvbG5hbWVzKHRlYy5uZWlnaGJvci5kZikgPC0gYygibG9nRkMiLCAiTmVpZ2hib3VyaG9vZCIsICJTcGF0aWFsRkRSIiwgIlVNQVAxIiwgIlVNQVAyIikKdGVjLm5laWdoYm9yLmRmJFNpZyA8LSBhcy5udW1lcmljKHRlYy5uZWlnaGJvci5kZiRTcGF0aWFsRkRSIDw9IDAuMDUpCgpnZ3Bsb3QodGVjLm5laWdoYm9yLmRmLCBhZXMoeD1VTUFQMSwgeT1VTUFQMikpICsKICBnZW9tX3BvaW50KGRhdGE9dGVjLm5laWdoYm9yLmRmW3RlYy5uZWlnaGJvci5kZiRTaWcgPT0gMCwgXSwKICAgICAgICAgICAgIGNvbG91cj0nZ3JleTgwJywgc2l6ZT0yKSArCiAgZ2VvbV9wb2ludChkYXRhPXRlYy5uZWlnaGJvci5kZlt0ZWMubmVpZ2hib3IuZGYkU2lnID09IDEsIF0sCiAgICAgICAgICAgICBhZXMoY29sb3VyPWxvZ0ZDKSwgc2l6ZT00KSArCiAgdGhlbWVfY2xlYW4oKSArCiAgc2NhbGVfY29sb3VyX2dyYWRpZW50Mihsb3c9ImJsdWUiLCBtaWQ9ImdyZXk4MCIsIGhpZ2g9InJlZCIpCmBgYAoKU29tZSBhcmUgdXAgYW5kIHNvbWUgYXJlIGRvd24uIFdoaWNoIFRFQyBzdWJ0eXBlcyBkbyB0aGV5IGxhcmdlbHkgY29ycmVzcG9uZCB3aXRoPwoKYGBge3J9CnRlYy5uZWlnaGJvdXIubGlzdCA8LSBsaXN0KCkKZm9yKHggaW4gc2VxX2Fsb25nKDE6bGVuZ3RoKHRlYy52ZXJ0ZXgubGlzdCkpKXsKICB4LmRmIDwtIHRlYy51bWFwLm1lcmdlW3RlYy51bWFwLm1lcmdlJFZlcnRleCAlaW4lIHRlYy52ZXJ0ZXgubGlzdFtbeF1dLCBdCiAgeC5yZXAgPC0gbmFtZXModGFibGUoeC5kZiRTb3J0RGF5KSlbd2hpY2godGFibGUoeC5kZiRTb3J0RGF5KSA9PSBtYXgodGFibGUoeC5kZiRTb3J0RGF5KSkpXQogIGlmKGxlbmd0aCh4LnJlcCkgPiAxKXsKICAgIHgucmVwIDwtIHNhbXBsZShzaXplPTEsIHgucmVwKQogIH0KICB4LmJsb2NrIDwtIG5hbWVzKHRhYmxlKHguZGYkQ2x1c3RlcikpW3doaWNoKHRhYmxlKHguZGYkQ2x1c3RlcikgPT0gbWF4KHRhYmxlKHguZGYkQ2x1c3RlcikpKV0KICAgIGlmKGxlbmd0aCh4LmJsb2NrKSA+IDEpewogICAgeC5ibG9jayA8LSBzYW1wbGUoc2l6ZT0xLCB4LmJsb2NrKQogIH0KICB4LmNvbmRpdGlvbiA8LSBuYW1lcyh0YWJsZSh4LmRmJEFnZSkpW3doaWNoKHRhYmxlKHguZGYkQWdlKSA9PSBtYXgodGFibGUoeC5kZiRBZ2UpKSldCiAgICBpZihsZW5ndGgoeC5jb25kaXRpb24pID4gMSl7CiAgICB4LmNvbmRpdGlvbiA8LSBzYW1wbGUoc2l6ZT0xLCB4LmNvbmRpdGlvbikKICB9CiAgCiAgdGVjLm5laWdoYm91ci5saXN0W1t4XV0gPC0gZGF0YS5mcmFtZSgiUmVwbGljYXRlIj14LnJlcCwgIkNsdXN0ZXIiPXguYmxvY2ssICJDb25kaXRpb24iPXguY29uZGl0aW9uLCAiTmVpZ2hib3VyaG9vZCI9eCkKfQoKdGVjLm5laWdoYm91ci5tZXRhIDwtIGRvLmNhbGwocmJpbmQuZGF0YS5mcmFtZSwgdGVjLm5laWdoYm91ci5saXN0KQp0ZWMubmVpZ2hib3VyLm1lcmdlIDwtIG1lcmdlKHRlYy5uZWlnaGJvci5kZiwgdGVjLm5laWdoYm91ci5tZXRhLCBieT0nTmVpZ2hib3VyaG9vZCcpCgp0ZWMubmVpZ2hib3VyLm1lcmdlJERpZmYgPC0gc2lnbih0ZWMubmVpZ2hib3VyLm1lcmdlJGxvZ0ZDKQp0ZWMubmVpZ2hib3VyLm1lcmdlJERpZmZbdGVjLm5laWdoYm91ci5tZXJnZSRTaWcgPT0gMF0gPC0gMApgYGAKCgpgYGB7ciwgZmlnLndpZHRoPTkuNzUsIGZpZy5oZWlnaHQ9NC41NX0KZ2dwbG90KHRlYy5uZWlnaGJvdXIubWVyZ2UsIGFlcyh4PVVNQVAxLCB5PVVNQVAyKSkgKwogIGdlb21fcG9pbnQoZGF0YT10ZWMubmVpZ2hib3VyLm1lcmdlWywgYygiVU1BUDEiLCAiVU1BUDIiKV0sCiAgICAgICAgICAgICBjb2xvdXI9J2dyZXk4MCcsIHNpemU9MSkgKwogIGdlb21fcG9pbnQoZGF0YT10ZWMubmVpZ2hib3VyLm1lcmdlW3RlYy5uZWlnaGJvdXIubWVyZ2UkU2lnID09IDEsIF0sCiAgICAgICAgICAgICBhZXMoY29sb3VyPWxvZ0ZDKSwgc2l6ZT00KSArCiAgdGhlbWVfY2xlYW4oKSArCiAgc2NhbGVfY29sb3VyX2dyYWRpZW50Mihsb3c9ImJsdWUiLCBtaWQ9ImdyZXk4MCIsIGhpZ2g9InJlZCIpICsKICBmYWNldF93cmFwKH5DbHVzdGVyKQpgYGAKClRoZXJlIGlzIGEgc3Vic2V0IG9mIHRoZSBQcm9saWZlcmF0aW5nIFRFQyB0aGF0IGFyZSBkb3duLCBnb29kLCBhcyBhcmUgdGhlIFBlcmluYXRhbCBjVEVDLiBMaWtld2lzZSwgbW9zdCBvZiB0aGUgSW50ZXJ0eXBpY2FsIFRFQyBhcmUgdXAgYXMgd2VsbCwgYnV0IHNvbWUgYXJlIGFsc28gZG93bi4gCkkgZG9uJ3Qga25vdyBpZiB0aGlzIGlzIGJlY2F1c2Ugb2YgYSBjb21wb3NpdGlvbmFsIGVmZmVjdCBvciBiZWNhdXNlIHRoZXNlIGFyZSB0aGUgb25lcyB0aGF0IHdvdWxkIGJlIGRpZmZlcmVudGlhdGluZyBpbnRvIG1URUMgdmlhIHRoZSBQcm9saWZlcmF0aW5nIFRFQyBjb21wYXJ0bWVudC4KCmBgYHtyfQp0YWJsZSh0ZWMubmVpZ2hib3VyLm1lcmdlJENsdXN0ZXIsIHRlYy5uZWlnaGJvdXIubWVyZ2UkRGlmZikKYGBgCgpJIHdvdWxkIHNheSB0aGF0IHRoZXNlIHJlc3VsdHMgbWFrZSBhIGxvdCBvZiBzZW5zZS4gQ2FuIEkgbm93IGV4dGVuZCB0aGlzIHRvIGluY2x1ZGUgYWxsIHRpbWUgcG9pbnRzIGFuZCBmaXQgYWdlIGFzIGEgbGluZWFyIG9yZGluYWwgdmFyaWFibGU/CgojIyBFeHRlbmRlZCBURUMgREEgdGVzdGluZy4KCmBgYHtyfQojIGV4Y2x1ZGUgdGVjaG5pY2FsIGFydGlmYWN0IGNsdXN0ZXIKdGVjLm1ldGEgPC0gdGVjLm1ldGFbIXRlYy5tZXRhJFRGSURGLkNsdXN0ZXIgJWluJSBjKDQpLCBdCnRlYy5zdWIubWV0YSA8LSB0ZWMubWV0YQp0ZWMuc3ViLm1ldGEkQWdlRmFjdG9yIDwtIG9yZGVyZWQodGVjLnN1Yi5tZXRhJEFnZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscz1jKCIxd2siLCAiNHdrIiwgIjE2d2siLCAiMzJ3ayIsICI1MndrIikpCiMgYWRkIHRoZSBsYWJlbCBhbm5vdGF0aW9uCnRlYy5zdWIubWV0YSRDbHVzdGVyIDwtICJVbmtub3duIgp0ZWMuc3ViLm1ldGEkQ2x1c3Rlclt0ZWMuc3ViLm1ldGEkVEZJREYuQ2x1c3RlciA9PSAiMiJdIDwtICJJbnRlcnR5cGljYWwgVEVDIgp0ZWMuc3ViLm1ldGEkQ2x1c3Rlclt0ZWMuc3ViLm1ldGEkVEZJREYuQ2x1c3RlciA9PSAiOSJdIDwtICJQZXJpbmF0YWwgY1RFQyIKdGVjLnN1Yi5tZXRhJENsdXN0ZXJbdGVjLnN1Yi5tZXRhJFRGSURGLkNsdXN0ZXIgPT0gIjMiXSA8LSAiTWF0dXJlIGNURUMiCnRlYy5zdWIubWV0YSRDbHVzdGVyW3RlYy5zdWIubWV0YSRURklERi5DbHVzdGVyID09ICI3Il0gPC0gIk1hdHVyZSBtVEVDIgp0ZWMuc3ViLm1ldGEkQ2x1c3Rlclt0ZWMuc3ViLm1ldGEkVEZJREYuQ2x1c3RlciA9PSAiMSJdIDwtICJQb3N0LUFpcmUgbVRFQyIKdGVjLnN1Yi5tZXRhJENsdXN0ZXJbdGVjLnN1Yi5tZXRhJFRGSURGLkNsdXN0ZXIgPT0gIjUiXSA8LSAiVHVmdC1saWtlIG1URUMiCnRlYy5zdWIubWV0YSRDbHVzdGVyW3RlYy5zdWIubWV0YSRURklERi5DbHVzdGVyID09ICI2Il0gPC0gIlByb2xpZmVyYXRpbmcgVEVDIgp0ZWMuc3ViLm1ldGEkQ2x1c3Rlclt0ZWMuc3ViLm1ldGEkVEZJREYuQ2x1c3RlciA9PSAiOCJdIDwtICJuVEVDIgp0ZWMuc3ViLm1ldGEkQ2x1c3Rlclt0ZWMuc3ViLm1ldGEkVEZJREYuQ2x1c3RlciA9PSAiMTAiXSA8LSAic1RFQyIKCmludGVyLmNvbHMgPC0gYygiIzk5NzBhYiIsICIjMzU5NzhmIiwgIiNCMGNkYzEiLCAiIzc2MmE4MyIsICIjMDE2NjVlIiwgIiNlN2Q0ZTgiLCAiI2RmYzI3ZCIsICIjOGM1MTBhIiAsIiNiZjgxMmQiKQpuYW1lcyhpbnRlci5jb2xzKSA8LSBjKCJQb3N0LUFpcmUgbVRFQyIsICdJbnRlcnR5cGljYWwgVEVDJywgJ01hdHVyZSBjVEVDJywgJ1R1ZnQtbGlrZSBtVEVDJywgCiAgICAgICAgICAgICAgICAgICAgICAgJ1Byb2xpZmVyYXRpbmcgVEVDJywgJ01hdHVyZSBtVEVDJywgJ25URUMnLCAnUGVyaW5hdGFsIGNURUMnLCAnc1RFQycpCmBgYAoKYGBge3J9CnRlYy5zdWIuZ2V4IDwtIHRlYy5nZXhbLCBjb2xuYW1lcyh0ZWMuZ2V4KSAlaW4lIHRlYy5zdWIubWV0YSRTYW1wbGVdCmBgYAoKSSdsbCBidWlsZCBhIGtOTi1ncmFwaCBmcm9tIHRoZSBmaXJzdCAzMCBQQ3MgcHJldmlvdXNseSBjb21wdXRlZCBvbiBhbGwgVEVDLgoKYGBge3J9CnNldC5zZWVkKDQyKQp0ZWMua25uIDwtIGJ1aWxkS05OR3JhcGgoeD1hcy5tYXRyaXgodGVjLnN1Yi5tZXRhWywgcGFzdGUwKCJQQyIsIDE6MzApXSksIGs9MjEsIGQ9TkEsIHRyYW5zcG9zZWQ9VFJVRSkKdGVjLmZyLmxheW91dCA8LSBsYXlvdXRfd2l0aF9mcih0ZWMua25uKQpwbG90KHRlYy5rbm4sIGxheW91dD10ZWMuZnIubGF5b3V0LCB2ZXJ0ZXguZnJhbWUuY29sb3I9J3NreWJsdWUnLCB2ZXJ0ZXguY29sb3I9J3NreWJsdWUnLCB2ZXJ0ZXgubGFiZWwuY29sb3I9J2JsYWNrJywgCiAgICAgdmVydGV4LmxhYmVsLmZhbWlseT0nSGVsdmV0aWNhJywgZWRnZS5jb2xvcj0nZ3JleTYwJywgdmVydGV4LmxhYmVsLmNleD0wLjksCiAgICAgdmVydGV4LmxhYmVsLmRpc3Q9MSwgZWRnZS5hcnJvdy5zaXplPTAuMikKYGBgCgpUaGlzIGlzIGEgZmFpcmx5IGRlbnNlbHkgY29ubmVjdGVkIG5ldHdvcmssIGhvdyBkb2VzIHRoZSBVTUFQIGxvb2s/CgpgYGB7cn0Kc2V0LnNlZWQoNDIpCnRlYy51bWFwIDwtIHVtYXAoYXMubWF0cml4KHRlYy5zdWIubWV0YVssIHBhc3RlMCgiUEMiLCAxOjMwKV0pLAogICAgICAgICAgICAgICAgIG5fY29tcG9uZW50cz0yLAogICAgICAgICAgICAgICAgIG5fbmVpZ2hib3JzPTIxLCBtZXRyaWM9J2V1Y2xpZGVhbicsCiAgICAgICAgICAgICAgICAgaW5pdD0ncmFuZG9tJywgbWluX2Rpc3Q9MC4yKQp0ZWMudW1hcC5kZiA8LSBhcy5kYXRhLmZyYW1lKHRlYy51bWFwJGxheW91dCkKY29sbmFtZXModGVjLnVtYXAuZGYpIDwtIGMoIlVNQVAxIiwgIlVNQVAyIikKdGVjLnVtYXAuZGYkU2FtcGxlIDwtIHRlYy5zdWIubWV0YSRTYW1wbGUKCnRlYy51bWFwLm1lcmdlIDwtIG1lcmdlKHRlYy51bWFwLmRmLCB0ZWMuc3ViLm1ldGEsIGJ5PSdTYW1wbGUnKQpgYGAKCgpgYGB7ciwgZmlnLndpZHRoPTkuOTUsIGZpZy5oZWlnaHQ9Ny4xNX0KZ2dwbG90KHRlYy51bWFwLm1lcmdlLCBhZXMoeD1VTUFQMSwgeT1VTUFQMikpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvdXI9Q2x1c3RlcikpICsKICB0aGVtZV9jbGVhbigpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1pbnRlci5jb2xzKSArCiAgZmFjZXRfd3JhcCh+QWdlRmFjdG9yKSArCiAgZ3VpZGVzKGNvbG91cj1ndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzPWxpc3Qoc2l6ZT0zKSksCiAgICAgICAgIHNoYXBlPWd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXM9bGlzdChzaXplPTMpKSkKYGBgCgoKYGBge3J9CiMgcmFuZG9tbHkgc2VsZWN0IHZlcnRpY2VzIGluIHRoZSBncmFwaApuLmhvb2QgPC0gMC4wNQp0ZWMucmFuZG9tLnZlcnRpY2VzIDwtIHNhbXBsZShWKHRlYy5rbm4pLCBzaXplPWZsb29yKG4uaG9vZCpsZW5ndGgoVih0ZWMua25uKSkpKQojIGxvb3Agb3ZlciByYW5kb20gdmVydGljZXMgYW5kIGNvdW50IHRoZSBudW1iZXIgb2YgY2VsbHMgaW4gZWFjaAp0ZWMudmVydGV4Lmxpc3QgPC0gc2FwcGx5KDE6bGVuZ3RoKHRlYy5yYW5kb20udmVydGljZXMpLCBGVU49ZnVuY3Rpb24oWCkgbmVpZ2hib3JzKHRlYy5rbm4sIHY9dGVjLnJhbmRvbS52ZXJ0aWNlc1tYXSkpCmhpc3QodW5saXN0KGxhcHBseSh0ZWMudmVydGV4Lmxpc3QsIGxlbmd0aCkpLCAxMDAsIG1haW49Ikhpc3RvZ3JhbSBvZiBuZWlnaGJvcnMiLCB4bGFiPSJOZWlnaGJvdXJob29kIHNpemUiKQpgYGAKClRoaXMgaXMgdGhlIGhpc3RvZ3JhbSBvZiBURUMgbmVpZ2hib3VyaG9vZCBzaXplcy4KCmBgYHtyfQp0ZWMudW1hcC5tZXJnZSRFeHBTYW1wIDwtIHBhc3RlKHRlYy51bWFwLm1lcmdlJEFnZSwgdGVjLnVtYXAubWVyZ2UkU29ydERheSwgc2VwPSJfIikKdGVjLnVtYXAubWVyZ2UkVmVydGV4IDwtIGMoMTpucm93KHRlYy51bWFwLm1lcmdlKSkKdGVjLmNvdW50cyA8LSBxdWFudF9uZWlnaGJvdXJob29kKGdyYXBoPXRlYy5rbm4sIG1ldGE9dGVjLnVtYXAubWVyZ2UsIHNhbXBsZS5jb2x1bW49J0V4cFNhbXAnLCBzYW1wbGUudmVydGljZXM9bi5ob29kKQpgYGAKCgpgYGB7cn0KdGVjLnJlcHMgPC0gdW5saXN0KGxhcHBseShzdHJzcGxpdCh1bmlxdWUodGVjLnVtYXAubWVyZ2UkRXhwU2FtcCksIHNwbGl0PSJfIiksIEZVTj1mdW5jdGlvbihYKSBwYXN0ZTAoWFsyXSkpKQp0ZWMuY29uZCA8LSB1bmxpc3QobGFwcGx5KHN0cnNwbGl0KHVuaXF1ZSh0ZWMudW1hcC5tZXJnZSRFeHBTYW1wKSwgc3BsaXQ9Il8iKSwgRlVOPWZ1bmN0aW9uKFgpIHBhc3RlMChYWzFdKSkpCgp0ZWMuc2FtcGxlLm1ldGEgPC0gZGF0YS5mcmFtZSgiQ29uZGl0aW9uIj10ZWMuY29uZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJlcGxpY2F0ZSI9dGVjLnJlcHMpCnRlYy5zYW1wbGUubWV0YSRTYW1wbGUgPC0gcGFzdGUodGVjLnNhbXBsZS5tZXRhJENvbmRpdGlvbiwgdGVjLnNhbXBsZS5tZXRhJFJlcGxpY2F0ZSwgc2VwPSJfIikKcm93bmFtZXModGVjLnNhbXBsZS5tZXRhKSA8LSB0ZWMuc2FtcGxlLm1ldGEkU2FtcGxlCnRlYy5zYW1wbGUubWV0YSRDb25kaXRpb24gPC0gb3JkZXJlZCh0ZWMuc2FtcGxlLm1ldGEkQ29uZGl0aW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzPWMoIjF3ayIsICI0d2siLCAiMTZ3ayIsICIzMndrIiwgIjUyd2siKSkKdGVjLm1vZGVsIDwtIG1vZGVsLm1hdHJpeCh+IENvbmRpdGlvbiwgZGF0YT10ZWMuc2FtcGxlLm1ldGEpCmhlYWQodGVjLm1vZGVsKQpgYGAKCmBgYHtyfQp0ZWMuZGdlIDwtIERHRUxpc3QodGVjLmNvdW50c1ssIHJvd25hbWVzKHRlYy5tb2RlbCldLCBsaWIuc2l6ZT1sb2coY29sU3Vtcyh0ZWMuY291bnRzWywgcm93bmFtZXModGVjLm1vZGVsKV0pKSkKdGVjLmRnZSA8LSBlc3RpbWF0ZURpc3AodGVjLmRnZSwgdGVjLm1vZGVsKQp0ZWMuZml0IDwtIGdsbVFMRml0KHRlYy5kZ2UsIHRlYy5tb2RlbCwgcm9idXN0PVRSVUUpCiMgdGVjLmNvbnRyYXN0IDwtIG1ha2VDb250cmFzdHMoQ29uZGl0aW9uQSAtIENvbmRpdGlvbkIsIGxldmVscz10ZWMubW9kZWwpCiMgdGVjLnJlcyA8LSBnbG1RTEZUZXN0KHRlYy5maXQsIGNvbnRyYXN0PXRlYy5jb250cmFzdCkKCnRlYy5yZXMgPC0gYXMuZGF0YS5mcmFtZSh0b3BUYWdzKGdsbVFMRlRlc3QodGVjLmZpdCwgY29lZj0yKSwgc29ydC5ieT0nbm9uZScsIG49SW5mKSkKdGVjLnJlcyRTaWcgPC0gYXMuZmFjdG9yKGFzLm51bWVyaWModGVjLnJlcyRGRFIgPD0gMC4wMSkpCnRlYy5yZXMkTmVpZ2hib3VyaG9vZCA8LSBhcy5udW1lcmljKHJvd25hbWVzKHRlYy5yZXMpKQoKIyBjb250cm9sIHRoZSBzcGF0aWFsIEZEUgpxdmFscyA8LSB0ZWMucmVzJFBWYWx1ZQppcy5zaWcgPC0gcXZhbHMgPD0gMC4wMQpzdW1tYXJ5KGlzLnNpZykKYGBgCgpUaGVyZSBhcmUgNDYgREEgbmVpZ2hib3VyaG9vZHMgLSBJIGV4cGVjdCB0aGVzZSBzaG91bGQgcmVmbGVjdCB0aGUgUGVyaW5hdGFsLCBJbnRlcnR5cGljYWwsIFByb2xpZmVyYXRpbmcgYW5kIHNURUMuIEknbGwgdXNlIHRoZSBkaXN0YW5jZS1iYXNlZCBhcHByb2FjaCB0byAKY29ycmVjdCBmb3IgdGhlIHNwYXRpYWwgRkRSLgoKYGBge3J9CnRlYy5zcGF0aWFsZmRyIDwtIGdyYXBoX3NwYXRpYWxGRFIobmVpZ2hib3Job29kcz10ZWMudmVydGV4Lmxpc3QsIGdyYXBoPXRlYy5rbm4sIGNvbm5lY3Rpdml0eT0iZGlzdGFuY2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBjYT1hcy5tYXRyaXgodGVjLnN1Yi5tZXRhWywgcGFzdGUwKCJQQyIsIDE6MzApXSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHZhbHVlcz10ZWMucmVzW29yZGVyKHRlYy5yZXMkTmVpZ2hib3VyaG9vZCksIF0kUFZhbHVlKQp0ZWMucmVzJFNwYXRpYWxGRFJbb3JkZXIodGVjLnJlcyROZWlnaGJvdXJob29kKV0gPC0gdGVjLnNwYXRpYWxmZHIKcXZhbHMgPC0gdGVjLnNwYXRpYWxmZHIKaXMuc2lnIDwtIHF2YWxzIDw9IDAuMDEKc3VtbWFyeShpcy5zaWcpCmBgYAoKSW50ZXJlc3RpbmcsIDEyIG5laWdoYm91cmhvb2RzIGFyZSBubyBsb25nZXIgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBhZnRlciB0aGUgc3BhdGlhbCBGRFIgY29ycmVjdGlvbiAtIGhvcGVmdWxseSB0aGVzZSBhcmUgZ2VudWluZWx5IGZhbHNlLXBvc2l0aXZlcy4KCkluIGVhY2ggbmVpZ2hib3VyaG9vZCwgd2hhdCBpcyB0aGUgbW9zdCBjb21tb24gY29uZGl0aW9uIG9yIGJsb2NrIG9mIGNlbGxzPwoKYGBge3J9CnRlYy5uZWlnaGJvdXIuZXhwcnMgPC0gbmVpZ2hib3Job29kX2V4cHJlc3Npb24odGVjLnZlcnRleC5saXN0LCB0ZWMuc3ViLmdleCkKYGBgCgpFbWJlZCB0aGVzZSBoeXBlcnNwaGVyZXMgd2l0aCBhIFBDQSBhbmQgVU1BUC4KCmBgYHtyfQp0ZWMubmVpZ2hib3VyLnBjYSA8LSBwcmNvbXAoKHQodGVjLm5laWdoYm91ci5leHByc1t0ZWMuaHZncyRIVkcsIF0pKSkKcGFpcnModGVjLm5laWdoYm91ci5wY2EkeFssIGMoMTo1KV0pCmBgYAoKYGBge3J9CnNldC5zZWVkKDQyKQpuZWlnaGJvdXJob29kLnVtYXAgPC0gdW1hcCh0ZWMubmVpZ2hib3VyLnBjYSR4WywgYygxOjMwKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fY29tcG9uZW50cz0yLAogICAgICAgICAgICAgICAgICAgICAgICAgICBuX25laWdoYm9ycz0yMSwgbWV0cmljPSdldWNsaWRlYW4nLAogICAgICAgICAgICAgICAgICAgICAgICAgICBpbml0PSdyYW5kb20nLCBtaW5fZGlzdD0wLjEpCmBgYAoKV2UgY2FuIG92ZXJsYXkgdGhlIERBIHRlc3Rpbmcgb24gdGhlc2UgbmVpZ2hib3VyaG9vZHMuCgpgYGB7cn0KdGVjLm5laWdoYm9yLmRmIDwtIHRlYy5yZXNbLCBjKCJsb2dGQyIsICJOZWlnaGJvdXJob29kIiwgIlNwYXRpYWxGRFIiKV0KdGVjLm5laWdoYm9yLmRmIDwtIGRvLmNhbGwoY2JpbmQuZGF0YS5mcmFtZSwgbGlzdCh0ZWMubmVpZ2hib3IuZGYsIGFzLmRhdGEuZnJhbWUobmVpZ2hib3VyaG9vZC51bWFwJGxheW91dCkpKQpjb2xuYW1lcyh0ZWMubmVpZ2hib3IuZGYpIDwtIGMoImxvZ0ZDIiwgIk5laWdoYm91cmhvb2QiLCAiU3BhdGlhbEZEUiIsICJVTUFQMSIsICJVTUFQMiIpCnRlYy5uZWlnaGJvci5kZiRTaWcgPC0gYXMubnVtZXJpYyh0ZWMubmVpZ2hib3IuZGYkU3BhdGlhbEZEUiA8PSAwLjA1KQoKZ2dwbG90KHRlYy5uZWlnaGJvci5kZiwgYWVzKHg9VU1BUDEsIHk9VU1BUDIpKSArCiAgZ2VvbV9wb2ludChkYXRhPXRlYy5uZWlnaGJvci5kZlt0ZWMubmVpZ2hib3IuZGYkU2lnID09IDAsIF0sCiAgICAgICAgICAgICBjb2xvdXI9J2dyZXk4MCcsIHNpemU9MikgKwogIGdlb21fcG9pbnQoZGF0YT10ZWMubmVpZ2hib3IuZGZbdGVjLm5laWdoYm9yLmRmJFNpZyA9PSAxLCBdLAogICAgICAgICAgICAgYWVzKGNvbG91cj1sb2dGQyksIHNpemU9NCkgKwogIHRoZW1lX2NsZWFuKCkgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudDIobG93PSJibHVlIiwgbWlkPSJncmV5ODAiLCBoaWdoPSJyZWQiKQpgYGAKClNvbWUgYXJlIHVwIGFuZCBzb21lIGFyZSBkb3duLiBXaGljaCBURUMgc3VidHlwZXMgZG8gdGhleSBsYXJnZWx5IGNvcnJlc3BvbmQgd2l0aD8gVGhhdCBiaWcgc3RyZWFrIG9mIGxvd2VyIGFidW5kYW5jZSBuZWlnaGJvdXJob29kcyBzaG91bGQgYmUgdGhlIGRpZmZlcmVudGlhdGlvbiAKdHJhamVjdG9yeSBmcm9tIEludGVydHlwaWNhbCB0byBNYXR1cmUgbVRFQy4KCmBgYHtyfQp0ZWMubmVpZ2hib3VyLmxpc3QgPC0gbGlzdCgpCmZvcih4IGluIHNlcV9hbG9uZygxOmxlbmd0aCh0ZWMudmVydGV4Lmxpc3QpKSl7CiAgeC5kZiA8LSB0ZWMudW1hcC5tZXJnZVt0ZWMudW1hcC5tZXJnZSRWZXJ0ZXggJWluJSB0ZWMudmVydGV4Lmxpc3RbW3hdXSwgXQogIHgucmVwIDwtIG5hbWVzKHRhYmxlKHguZGYkU29ydERheSkpW3doaWNoKHRhYmxlKHguZGYkU29ydERheSkgPT0gbWF4KHRhYmxlKHguZGYkU29ydERheSkpKV0KICBpZihsZW5ndGgoeC5yZXApID4gMSl7CiAgICB4LnJlcCA8LSBzYW1wbGUoc2l6ZT0xLCB4LnJlcCkKICB9CiAgeC5ibG9jayA8LSBuYW1lcyh0YWJsZSh4LmRmJENsdXN0ZXIpKVt3aGljaCh0YWJsZSh4LmRmJENsdXN0ZXIpID09IG1heCh0YWJsZSh4LmRmJENsdXN0ZXIpKSldCiAgICBpZihsZW5ndGgoeC5ibG9jaykgPiAxKXsKICAgIHguYmxvY2sgPC0gc2FtcGxlKHNpemU9MSwgeC5ibG9jaykKICB9CiAgeC5jb25kaXRpb24gPC0gbmFtZXModGFibGUoeC5kZiRBZ2UpKVt3aGljaCh0YWJsZSh4LmRmJEFnZSkgPT0gbWF4KHRhYmxlKHguZGYkQWdlKSkpXQogICAgaWYobGVuZ3RoKHguY29uZGl0aW9uKSA+IDEpewogICAgeC5jb25kaXRpb24gPC0gc2FtcGxlKHNpemU9MSwgeC5jb25kaXRpb24pCiAgfQogIAogIHRlYy5uZWlnaGJvdXIubGlzdFtbeF1dIDwtIGRhdGEuZnJhbWUoIlJlcGxpY2F0ZSI9eC5yZXAsICJDbHVzdGVyIj14LmJsb2NrLCAiQ29uZGl0aW9uIj14LmNvbmRpdGlvbiwgIk5laWdoYm91cmhvb2QiPXgpCn0KCnRlYy5uZWlnaGJvdXIubWV0YSA8LSBkby5jYWxsKHJiaW5kLmRhdGEuZnJhbWUsIHRlYy5uZWlnaGJvdXIubGlzdCkKdGVjLm5laWdoYm91ci5tZXJnZSA8LSBtZXJnZSh0ZWMubmVpZ2hib3IuZGYsIHRlYy5uZWlnaGJvdXIubWV0YSwgYnk9J05laWdoYm91cmhvb2QnKQoKdGVjLm5laWdoYm91ci5tZXJnZSREaWZmIDwtIHNpZ24odGVjLm5laWdoYm91ci5tZXJnZSRsb2dGQykKdGVjLm5laWdoYm91ci5tZXJnZSREaWZmW3RlYy5uZWlnaGJvdXIubWVyZ2UkU2lnID09IDBdIDwtIDAKYGBgCgoKYGBge3IsIGZpZy53aWR0aD05Ljc1LCBmaWcuaGVpZ2h0PTUuMTV9CmdncGxvdCh0ZWMubmVpZ2hib3VyLm1lcmdlLCBhZXMoeD1VTUFQMSwgeT1VTUFQMikpICsKICBnZW9tX3BvaW50KGRhdGE9dGVjLm5laWdoYm91ci5tZXJnZVssIGMoIlVNQVAxIiwgIlVNQVAyIildLAogICAgICAgICAgICAgY29sb3VyPSdncmV5ODAnLCBzaXplPTEpICsKICBnZW9tX3BvaW50KGRhdGE9dGVjLm5laWdoYm91ci5tZXJnZVt0ZWMubmVpZ2hib3VyLm1lcmdlJFNpZyA9PSAxLCBdLAogICAgICAgICAgICAgYWVzKGNvbG91cj1sb2dGQyksIHNpemU9NCkgKwogIHRoZW1lX2NsZWFuKCkgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudDIobG93PSJibHVlIiwgbWlkPSJncmV5ODAiLCBoaWdoPSJyZWQiKSArCiAgZmFjZXRfd3JhcCh+Q2x1c3RlcikKYGBgCgpUaGlzIHZlcnkgbmljZWx5IHJlY2FwaXR1bGF0ZXMgdGhlIERBIHRlc3RpbmcgdXNpbmcgY2x1c3RlcnMsIGFuZCBpdCBwaW5wb2ludHMgdGhlIGxvc3Mgb2YgbWVkdWxsYS1iaWFzZWQgSW50ZXJ0eXBpY2FsIFRFQyB3aGljaCB3ZSBvbmx5IHJlYWxseSBvYnNlcnZlZCBpbiBvdXIgbGFyZ2VyIApleHBlcmltZW50cy4gVGhpcyBpcyB3b3JraW5nIGJleW9uZCBteSB3aWxkZXN0IGRyZWFtcyEhCgpgYGB7cn0KdGFibGUodGVjLm5laWdoYm91ci5tZXJnZSRDbHVzdGVyLCB0ZWMubmVpZ2hib3VyLm1lcmdlJERpZmYpCmBgYAoKSSB3b3VsZCBzYXkgdGhhdCB0aGVzZSByZXN1bHRzIG1ha2UgYSBsb3Qgb2Ygc2Vuc2UuIEknbGwgZXh0ZW5kIGl0IHRvIGluY2x1ZGUgdGhlIHF1YWRyYXRpYyB0ZXN0aW5nIHdoaWNoIHNob3VsZCBwaWNrIHVwIHRoZSBpbnZlcnNlLXBhcmFib2xpYyBwcm9maWxlIG9mIHRoZSAKUG9zdC1BaXJlIG1URUMgcG9wdWxhdGlvbi4KCmBgYHtyfQpxdWFkLnRlYy5yZXMgPC0gYXMuZGF0YS5mcmFtZSh0b3BUYWdzKGdsbVFMRlRlc3QodGVjLmZpdCwgY29lZj0zKSwgc29ydC5ieT0nbm9uZScsIG49SW5mKSkKcXVhZC50ZWMucmVzJFNpZyA8LSBhcy5mYWN0b3IoYXMubnVtZXJpYyhxdWFkLnRlYy5yZXMkRkRSIDw9IDAuMDEpKQpxdWFkLnRlYy5yZXMkTmVpZ2hib3VyaG9vZCA8LSBhcy5udW1lcmljKHJvd25hbWVzKHF1YWQudGVjLnJlcykpCgojIGNvbnRyb2wgdGhlIHNwYXRpYWwgRkRSCnF2YWxzIDwtIHF1YWQudGVjLnJlcyRQVmFsdWUKaXMuc2lnIDwtIHF2YWxzIDw9IDAuMDEKc3VtbWFyeShpcy5zaWcpCmBgYAoKVGhlcmUgYXJlIDYgREEgbmVpZ2hib3VyaG9vZHMgZnJvbSB0aGUgcXVhZHJhdGljIG1vZGVsIC0gSSBleHBlY3QgdGhlc2Ugc2hvdWxkIHJlZmxlY3QgdGhlIFBvc3QtQWlyZSBtVEVDLiAKCmBgYHtyfQpxdWFkLnRlYy5zcGF0aWFsZmRyIDwtIGdyYXBoX3NwYXRpYWxGRFIobmVpZ2hib3Job29kcz10ZWMudmVydGV4Lmxpc3QsIGdyYXBoPXRlYy5rbm4sIGNvbm5lY3Rpdml0eT0iZGlzdGFuY2UiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBjYT1hcy5tYXRyaXgodGVjLnN1Yi5tZXRhWywgcGFzdGUwKCJQQyIsIDE6MzApXSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHZhbHVlcz1xdWFkLnRlYy5yZXNbb3JkZXIocXVhZC50ZWMucmVzJE5laWdoYm91cmhvb2QpLCBdJFBWYWx1ZSkKcXVhZC50ZWMucmVzJFNwYXRpYWxGRFJbb3JkZXIocXVhZC50ZWMucmVzJE5laWdoYm91cmhvb2QpXSA8LSBxdWFkLnRlYy5zcGF0aWFsZmRyCnF2YWxzIDwtIHF1YWQudGVjLnNwYXRpYWxmZHIKaXMuc2lnIDwtIHF2YWxzIDw9IDAuMDUKc3VtbWFyeShpcy5zaWcpCmBgYAoKRGFuZywgY2xlYXJseSB0aGUgc21hbGwgZ3JvdXAgb2YgUG9zdC1BaXJlIG1URUMgbWVhbnMgdGhpcyBpc24ndCBzdWZmaWNpZW50bHkgc2Vuc2l0aXZlIGFmdGVyIG11bHRpcGxlLXRlc3RpbmcgY29ycmVjdGlvbi4KCmBgYHtyLCBmaWcud2lkdGg9OS43NSwgZmlnLmhlaWdodD01LjE1fQpnZ3Bsb3QodGVjLm5laWdoYm91ci5tZXJnZSwgYWVzKHg9VU1BUDEsIHk9VU1BUDIpKSArCiAgZ2VvbV9wb2ludChkYXRhPXRlYy5uZWlnaGJvdXIubWVyZ2VbLCBjKCJVTUFQMSIsICJVTUFQMiIpXSwKICAgICAgICAgICAgIGNvbG91cj0nZ3JleTgwJywgc2l6ZT0xKSArCiAgZ2VvbV9wb2ludChkYXRhPXRlYy5uZWlnaGJvdXIubWVyZ2UsCiAgICAgICAgICAgICBhZXMoY29sb3VyPUNsdXN0ZXIpLCBzaXplPTQpICsKICB0aGVtZV9jbGVhbigpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1pbnRlci5jb2xzKSArCiAgZmFjZXRfd3JhcCh+Q2x1c3RlcikKYGBgCgpIbW0sIHRoZSBQb3N0LUFpcmUgbVRFQyBkb24ndCBmb3JtIGEgc2luZ2xlIG5laWdoYm91cmhvb2Qgb24gdGhlaXIgb3duLCBub3IgZG8gdGhlIG5URUMgb3IgVHVmdC1saWtlIG1URUMuIFRoZXJlIGlzIGRlZmluaXRlbHkgYSBsaW1pdCB0byB0aGUgcmVzb2x1dGlvbi4gV291bGQgYSAKc21hbGxlciAkayQgcmVzb2x2ZSB0aGlzPwoKIyMgQWdlaW5nIFRFQyB3aXRoIGs9MTEKCmBgYHtyfQp0ZWMuc3ViLm1ldGEgPC0gdGVjLm1ldGFbdGVjLm1ldGEkQWdlICVpbiUgYygiMXdrIiwgIjUyd2siKSwgXQoKIyBhZGQgdGhlIGxhYmVsIGFubm90YXRpb24KdGVjLnN1Yi5tZXRhJENsdXN0ZXIgPC0gIlVua25vd24iCnRlYy5zdWIubWV0YSRDbHVzdGVyW3RlYy5zdWIubWV0YSRURklERi5DbHVzdGVyID09ICIyIl0gPC0gIkludGVydHlwaWNhbCBURUMiCnRlYy5zdWIubWV0YSRDbHVzdGVyW3RlYy5zdWIubWV0YSRURklERi5DbHVzdGVyID09ICI5Il0gPC0gIlBlcmluYXRhbCBjVEVDIgp0ZWMuc3ViLm1ldGEkQ2x1c3Rlclt0ZWMuc3ViLm1ldGEkVEZJREYuQ2x1c3RlciA9PSAiMyJdIDwtICJNYXR1cmUgY1RFQyIKdGVjLnN1Yi5tZXRhJENsdXN0ZXJbdGVjLnN1Yi5tZXRhJFRGSURGLkNsdXN0ZXIgPT0gIjciXSA8LSAiTWF0dXJlIG1URUMiCnRlYy5zdWIubWV0YSRDbHVzdGVyW3RlYy5zdWIubWV0YSRURklERi5DbHVzdGVyID09ICIxIl0gPC0gIlBvc3QtQWlyZSBtVEVDIgp0ZWMuc3ViLm1ldGEkQ2x1c3Rlclt0ZWMuc3ViLm1ldGEkVEZJREYuQ2x1c3RlciA9PSAiNSJdIDwtICJUdWZ0LWxpa2UgbVRFQyIKdGVjLnN1Yi5tZXRhJENsdXN0ZXJbdGVjLnN1Yi5tZXRhJFRGSURGLkNsdXN0ZXIgPT0gIjYiXSA8LSAiUHJvbGlmZXJhdGluZyBURUMiCnRlYy5zdWIubWV0YSRDbHVzdGVyW3RlYy5zdWIubWV0YSRURklERi5DbHVzdGVyID09ICI4Il0gPC0gIm5URUMiCnRlYy5zdWIubWV0YSRDbHVzdGVyW3RlYy5zdWIubWV0YSRURklERi5DbHVzdGVyID09ICIxMCJdIDwtICJzVEVDIgoKaW50ZXIuY29scyA8LSBjKCIjOTk3MGFiIiwgIiMzNTk3OGYiLCAiI0IwY2RjMSIsICIjNzYyYTgzIiwgIiMwMTY2NWUiLCAiI2U3ZDRlOCIsICIjZGZjMjdkIiwgIiM4YzUxMGEiICwiI2JmODEyZCIpCm5hbWVzKGludGVyLmNvbHMpIDwtIGMoIlBvc3QtQWlyZSBtVEVDIiwgJ0ludGVydHlwaWNhbCBURUMnLCAnTWF0dXJlIGNURUMnLCAnVHVmdC1saWtlIG1URUMnLCAKICAgICAgICAgICAgICAgICAgICAgICAnUHJvbGlmZXJhdGluZyBURUMnLCAnTWF0dXJlIG1URUMnLCAnblRFQycsICdQZXJpbmF0YWwgY1RFQycsICdzVEVDJykKYGBgCgpJJ2xsIGJ1aWxkIGEga05OLWdyYXBoIGZyb20gdGhlIGZpcnN0IDMwIFBDcyBwcmV2aW91c2x5IGNvbXB1dGVkIG9uIGFsbCBURUMsIGJ1dCBrPTExIHRoaXMgdGltZS4KCmBgYHtyfQpzZXQuc2VlZCg0MikKdGVjLmtubiA8LSBidWlsZEtOTkdyYXBoKHg9YXMubWF0cml4KHRlYy5zdWIubWV0YVssIHBhc3RlMCgiUEMiLCAxOjMwKV0pLCBrPTExLCBkPU5BLCB0cmFuc3Bvc2VkPVRSVUUpCnRlYy5mci5sYXlvdXQgPC0gbGF5b3V0X3dpdGhfZnIodGVjLmtubikKcGxvdCh0ZWMua25uLCBsYXlvdXQ9dGVjLmZyLmxheW91dCwgdmVydGV4LmZyYW1lLmNvbG9yPSdza3libHVlJywgdmVydGV4LmNvbG9yPSdza3libHVlJywgdmVydGV4LmxhYmVsLmNvbG9yPSdibGFjaycsIAogICAgIHZlcnRleC5sYWJlbC5mYW1pbHk9J0hlbHZldGljYScsIGVkZ2UuY29sb3I9J2dyZXk2MCcsIHZlcnRleC5sYWJlbC5jZXg9MC45LAogICAgIHZlcnRleC5sYWJlbC5kaXN0PTEsIGVkZ2UuYXJyb3cuc2l6ZT0wLjIpCmBgYAoKVGhpcyBpcyBhIGZhaXJseSBkZW5zZWx5IGNvbm5lY3RlZCBuZXR3b3JrIHN0aWxsLCBldmVuIHdpdGggaz0xMSwgaG93IGRvZXMgdGhlIFVNQVAgbG9vaz8KCmBgYHtyfQpzZXQuc2VlZCg0MikKdGVjLnVtYXAgPC0gdW1hcChhcy5tYXRyaXgodGVjLnN1Yi5tZXRhWywgcGFzdGUwKCJQQyIsIDE6MzApXSksCiAgICAgICAgICAgICAgICAgbl9jb21wb25lbnRzPTIsCiAgICAgICAgICAgICAgICAgbl9uZWlnaGJvcnM9MTEsIG1ldHJpYz0nZXVjbGlkZWFuJywKICAgICAgICAgICAgICAgICBpbml0PSdyYW5kb20nLCBtaW5fZGlzdD0wLjIpCnRlYy51bWFwLmRmIDwtIGFzLmRhdGEuZnJhbWUodGVjLnVtYXAkbGF5b3V0KQpjb2xuYW1lcyh0ZWMudW1hcC5kZikgPC0gYygiVU1BUDEiLCAiVU1BUDIiKQp0ZWMudW1hcC5kZiRTYW1wbGUgPC0gdGVjLnN1Yi5tZXRhJFNhbXBsZQoKdGVjLnVtYXAubWVyZ2UgPC0gbWVyZ2UodGVjLnVtYXAuZGYsIHRlYy5zdWIubWV0YSwgYnk9J1NhbXBsZScpCmBgYAoKCmBgYHtyLCBmaWcud2lkdGg9Ny45NSwgZmlnLmhlaWdodD00LjE1fQpnZ3Bsb3QodGVjLnVtYXAubWVyZ2UsIGFlcyh4PVVNQVAxLCB5PVVNQVAyKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG91cj1DbHVzdGVyKSkgKwogIHRoZW1lX2NsZWFuKCkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWludGVyLmNvbHMpICsKICBmYWNldF93cmFwKH5BZ2UpICsKICBndWlkZXMoY29sb3VyPWd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXM9bGlzdChzaXplPTMpKSwKICAgICAgICAgc2hhcGU9Z3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcz1saXN0KHNpemU9MykpKQpgYGAKCkZvciBhIHNtYWxsZXIgaywgZG9lcyB0aGVyZSBuZWVkIHRvIGJlIGEgaGlnaGVyIGRlbnNpdHkgb2Ygc2FtcGxpbmcsIGkuZS4gbW9yZSBuZWlnaGJvdXJob29kcz8gSSdsbCBzZXQgaXQgdG8gMTAlIGhlcmUgaW5zdGVhZC4KCmBgYHtyfQojIHJhbmRvbWx5IHNlbGVjdCB2ZXJ0aWNlcyBpbiB0aGUgZ3JhcGgKbi5ob29kIDwtIDAuMTAKdGVjLnJhbmRvbS52ZXJ0aWNlcyA8LSBzYW1wbGUoVih0ZWMua25uKSwgc2l6ZT1mbG9vcihuLmhvb2QqbGVuZ3RoKFYodGVjLmtubikpKSkKIyBsb29wIG92ZXIgcmFuZG9tIHZlcnRpY2VzIGFuZCBjb3VudCB0aGUgbnVtYmVyIG9mIGNlbGxzIGluIGVhY2gKdGVjLnZlcnRleC5saXN0IDwtIHNhcHBseSgxOmxlbmd0aCh0ZWMucmFuZG9tLnZlcnRpY2VzKSwgRlVOPWZ1bmN0aW9uKFgpIG5laWdoYm9ycyh0ZWMua25uLCB2PXRlYy5yYW5kb20udmVydGljZXNbWF0pKQpoaXN0KHVubGlzdChsYXBwbHkodGVjLnZlcnRleC5saXN0LCBsZW5ndGgpKSwgMTAwLCBtYWluPSJIaXN0b2dyYW0gb2YgbmVpZ2hib3JzIiwgeGxhYj0iTmVpZ2hib3VyaG9vZCBzaXplIikKYGBgCgpUaGlzIGlzIHRoZSBoaXN0b2dyYW0gb2YgVEVDIG5laWdoYm91cmhvb2Qgc2l6ZXMuCgpgYGB7cn0KdGVjLnVtYXAubWVyZ2UkRXhwU2FtcCA8LSBwYXN0ZSh0ZWMudW1hcC5tZXJnZSRBZ2UsIHRlYy51bWFwLm1lcmdlJFNvcnREYXksIHNlcD0iXyIpCnRlYy51bWFwLm1lcmdlJFZlcnRleCA8LSBjKDE6bnJvdyh0ZWMudW1hcC5tZXJnZSkpCnRlYy5jb3VudHMgPC0gcXVhbnRfbmVpZ2hib3VyaG9vZChncmFwaD10ZWMua25uLCBtZXRhPXRlYy51bWFwLm1lcmdlLCBzYW1wbGUuY29sdW1uPSdFeHBTYW1wJywgc2FtcGxlLnZlcnRpY2VzPW4uaG9vZCkKYGBgCgoKYGBge3J9CnRlYy5yZXBzIDwtIHVubGlzdChsYXBwbHkoc3Ryc3BsaXQodW5pcXVlKHRlYy51bWFwLm1lcmdlJEV4cFNhbXApLCBzcGxpdD0iXyIpLCBGVU49ZnVuY3Rpb24oWCkgcGFzdGUwKFhbMl0pKSkKdGVjLmNvbmQgPC0gdW5saXN0KGxhcHBseShzdHJzcGxpdCh1bmlxdWUodGVjLnVtYXAubWVyZ2UkRXhwU2FtcCksIHNwbGl0PSJfIiksIEZVTj1mdW5jdGlvbihYKSBwYXN0ZTAoWFsxXSkpKQoKdGVjLnNhbXBsZS5tZXRhIDwtIGRhdGEuZnJhbWUoIkNvbmRpdGlvbiI9dGVjLmNvbmQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJSZXBsaWNhdGUiPXRlYy5yZXBzKQp0ZWMuc2FtcGxlLm1ldGEkU2FtcGxlIDwtIHBhc3RlKHRlYy5zYW1wbGUubWV0YSRDb25kaXRpb24sIHRlYy5zYW1wbGUubWV0YSRSZXBsaWNhdGUsIHNlcD0iXyIpCnJvd25hbWVzKHRlYy5zYW1wbGUubWV0YSkgPC0gdGVjLnNhbXBsZS5tZXRhJFNhbXBsZQoKdGVjLm1vZGVsIDwtIG1vZGVsLm1hdHJpeCh+IENvbmRpdGlvbiwgZGF0YT10ZWMuc2FtcGxlLm1ldGEpCmhlYWQodGVjLm1vZGVsKQpgYGAKCmBgYHtyfQp0ZWMuZGdlIDwtIERHRUxpc3QodGVjLmNvdW50c1ssIHJvd25hbWVzKHRlYy5tb2RlbCldLCBsaWIuc2l6ZT1sb2coY29sU3Vtcyh0ZWMuY291bnRzWywgcm93bmFtZXModGVjLm1vZGVsKV0pKSkKdGVjLmRnZSA8LSBlc3RpbWF0ZURpc3AodGVjLmRnZSwgdGVjLm1vZGVsKQp0ZWMuZml0IDwtIGdsbVFMRml0KHRlYy5kZ2UsIHRlYy5tb2RlbCwgcm9idXN0PVRSVUUpCiMgdGVjLmNvbnRyYXN0IDwtIG1ha2VDb250cmFzdHMoQ29uZGl0aW9uQSAtIENvbmRpdGlvbkIsIGxldmVscz10ZWMubW9kZWwpCiMgdGVjLnJlcyA8LSBnbG1RTEZUZXN0KHRlYy5maXQsIGNvbnRyYXN0PXRlYy5jb250cmFzdCkKCnRlYy5yZXMgPC0gYXMuZGF0YS5mcmFtZSh0b3BUYWdzKGdsbVFMRlRlc3QodGVjLmZpdCwgY29lZj0yKSwgc29ydC5ieT0nbm9uZScsIG49SW5mKSkKdGVjLnJlcyRTaWcgPC0gYXMuZmFjdG9yKGFzLm51bWVyaWModGVjLnJlcyRGRFIgPD0gMC4wNSkpCnRlYy5yZXMkTmVpZ2hib3VyaG9vZCA8LSBhcy5udW1lcmljKHJvd25hbWVzKHRlYy5yZXMpKQoKIyBjb250cm9sIHRoZSBzcGF0aWFsIEZEUgpxdmFscyA8LSB0ZWMucmVzJFBWYWx1ZQppcy5zaWcgPC0gcXZhbHMgPD0gMC4wNQpzdW1tYXJ5KGlzLnNpZykKYGBgCgpJIGhhdmUgaW5jcmVhc2VkIHRoZSB0b3RhbCBudW1iZXIgb2YgbmVpZ2hib3VyaG9vZHMgZGVmaW5lZCB3aXRoIGs9MTEsIHRoZXJlIHdpbGwgYWxtb3N0IGNlcnRhaW5seSBiZSBhIHRyYWRlLW9mZiBiZXR3ZWVuIHNlbnNpdGl2aXR5IGFuZCBwb3dlciB3LnIudC4gdGhlIGV4dHJhIAptdWx0aXBsZS10ZXN0aW5nIGJ1cmRlbiwgYXMgd2VsbCBhcyB0aGUgaGlnaGVyIHNhbXBsaW5nIHZhcmlhbmNlIGFzIGVhY2ggbmVpZ2hib3VyaG9vZCB3aWxsIGNvbnRhaW4gZmV3ZXIgY2VsbHMgPC0gdGhpcyB3aWxsIGJlIHNvbWV0aGluZyB3ZSBuZWVkIHRvIG9wdGltaXNlIGluIApzb21lIHdheS4KCmBgYHtyfQp0ZWMuc3BhdGlhbGZkciA8LSBncmFwaF9zcGF0aWFsRkRSKG5laWdoYm9yaG9vZHM9dGVjLnZlcnRleC5saXN0LCBncmFwaD10ZWMua25uLCBjb25uZWN0aXZpdHk9ImRpc3RhbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwY2E9YXMubWF0cml4KHRlYy5zdWIubWV0YVssIHBhc3RlMCgiUEMiLCAxOjMwKV0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHB2YWx1ZXM9dGVjLnJlc1tvcmRlcih0ZWMucmVzJE5laWdoYm91cmhvb2QpLCBdJFBWYWx1ZSkKdGVjLnJlcyRTcGF0aWFsRkRSW29yZGVyKHRlYy5yZXMkTmVpZ2hib3VyaG9vZCldIDwtIHRlYy5zcGF0aWFsZmRyCnF2YWxzIDwtIHRlYy5zcGF0aWFsZmRyCmlzLnNpZyA8LSBxdmFscyA8PSAwLjA1CnN1bW1hcnkoaXMuc2lnKQpgYGAKCkludGVyZXN0aW5nLCAxMSBvZiB0aGUgbmVpZ2hib3VyaG9vZHMgYXJlIG5vIGxvbmcgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBhZnRlciB0aGUgc3BhdGlhbCBGRFIgY29ycmVjdGlvbi4gQ2xlYXJseSB0aGVyZSBpcyBhIHRyYWRlLW9mZiBiZXR3ZWVuIG5laWdoYm91cmhvb2QgCnNpemUsIHNlbnNpdGl2aXR5IGFuZCBwb3dlci4KCkluIGVhY2ggbmVpZ2hib3VyaG9vZCwgd2hhdCBpcyB0aGUgbW9zdCBjb21tb24gY29uZGl0aW9uIG9yIGJsb2NrIG9mIGNlbGxzPwoKYGBge3J9CnRlYy5uZWlnaGJvdXIuZXhwcnMgPC0gbmVpZ2hib3Job29kX2V4cHJlc3Npb24odGVjLnZlcnRleC5saXN0LCB0ZWMuc3ViLmdleCkKYGBgCgpFbWJlZCB0aGVzZSBoeXBlcnNwaGVyZXMgd2l0aCBhIFBDQSBhbmQgVU1BUC4KCmBgYHtyfQp0ZWMubmVpZ2hib3VyLnBjYSA8LSBwcmNvbXAoKHQodGVjLm5laWdoYm91ci5leHByc1t0ZWMuaHZncyRIVkcsIF0pKSkKcGFpcnModGVjLm5laWdoYm91ci5wY2EkeFssIGMoMTo1KV0pCmBgYAoKYGBge3J9CnNldC5zZWVkKDQyKQpuZWlnaGJvdXJob29kLnVtYXAgPC0gdW1hcCh0ZWMubmVpZ2hib3VyLnBjYSR4WywgYygxOjMwKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fY29tcG9uZW50cz0yLAogICAgICAgICAgICAgICAgICAgICAgICAgICBuX25laWdoYm9ycz0xMSwgbWV0cmljPSdldWNsaWRlYW4nLAogICAgICAgICAgICAgICAgICAgICAgICAgICBpbml0PSdyYW5kb20nLCBtaW5fZGlzdD0wLjEpCmBgYAoKV2UgY2FuIG92ZXJsYXkgdGhlIERBIHRlc3Rpbmcgb24gdGhlc2UgbmVpZ2hib3VyaG9vZHMuCgpgYGB7cn0KdGVjLm5laWdoYm9yLmRmIDwtIHRlYy5yZXNbLCBjKCJsb2dGQyIsICJOZWlnaGJvdXJob29kIiwgIlNwYXRpYWxGRFIiKV0KdGVjLm5laWdoYm9yLmRmIDwtIGRvLmNhbGwoY2JpbmQuZGF0YS5mcmFtZSwgbGlzdCh0ZWMubmVpZ2hib3IuZGYsIGFzLmRhdGEuZnJhbWUobmVpZ2hib3VyaG9vZC51bWFwJGxheW91dCkpKQpjb2xuYW1lcyh0ZWMubmVpZ2hib3IuZGYpIDwtIGMoImxvZ0ZDIiwgIk5laWdoYm91cmhvb2QiLCAiU3BhdGlhbEZEUiIsICJVTUFQMSIsICJVTUFQMiIpCnRlYy5uZWlnaGJvci5kZiRTaWcgPC0gYXMubnVtZXJpYyh0ZWMubmVpZ2hib3IuZGYkU3BhdGlhbEZEUiA8PSAwLjA1KQoKZ2dwbG90KHRlYy5uZWlnaGJvci5kZiwgYWVzKHg9VU1BUDEsIHk9VU1BUDIpKSArCiAgZ2VvbV9wb2ludChkYXRhPXRlYy5uZWlnaGJvci5kZlt0ZWMubmVpZ2hib3IuZGYkU2lnID09IDAsIF0sCiAgICAgICAgICAgICBjb2xvdXI9J2dyZXk4MCcsIHNpemU9MikgKwogIGdlb21fcG9pbnQoZGF0YT10ZWMubmVpZ2hib3IuZGZbdGVjLm5laWdoYm9yLmRmJFNpZyA9PSAxLCBdLAogICAgICAgICAgICAgYWVzKGNvbG91cj1sb2dGQyksIHNpemU9NCkgKwogIHRoZW1lX2NsZWFuKCkgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudDIobG93PSJibHVlIiwgbWlkPSJncmV5ODAiLCBoaWdoPSJyZWQiKQpgYGAKClNvbWUgYXJlIHVwIGFuZCBzb21lIGFyZSBkb3duLiBXaGljaCBURUMgc3VidHlwZXMgZG8gdGhleSBsYXJnZWx5IGNvcnJlc3BvbmQgd2l0aD8KCmBgYHtyfQp0ZWMubmVpZ2hib3VyLmxpc3QgPC0gbGlzdCgpCmZvcih4IGluIHNlcV9hbG9uZygxOmxlbmd0aCh0ZWMudmVydGV4Lmxpc3QpKSl7CiAgeC5kZiA8LSB0ZWMudW1hcC5tZXJnZVt0ZWMudW1hcC5tZXJnZSRWZXJ0ZXggJWluJSB0ZWMudmVydGV4Lmxpc3RbW3hdXSwgXQogIHgucmVwIDwtIG5hbWVzKHRhYmxlKHguZGYkU29ydERheSkpW3doaWNoKHRhYmxlKHguZGYkU29ydERheSkgPT0gbWF4KHRhYmxlKHguZGYkU29ydERheSkpKV0KICBpZihsZW5ndGgoeC5yZXApID4gMSl7CiAgICB4LnJlcCA8LSBzYW1wbGUoc2l6ZT0xLCB4LnJlcCkKICB9CiAgeC5ibG9jayA8LSBuYW1lcyh0YWJsZSh4LmRmJENsdXN0ZXIpKVt3aGljaCh0YWJsZSh4LmRmJENsdXN0ZXIpID09IG1heCh0YWJsZSh4LmRmJENsdXN0ZXIpKSldCiAgICBpZihsZW5ndGgoeC5ibG9jaykgPiAxKXsKICAgIHguYmxvY2sgPC0gc2FtcGxlKHNpemU9MSwgeC5ibG9jaykKICB9CiAgeC5jb25kaXRpb24gPC0gbmFtZXModGFibGUoeC5kZiRBZ2UpKVt3aGljaCh0YWJsZSh4LmRmJEFnZSkgPT0gbWF4KHRhYmxlKHguZGYkQWdlKSkpXQogICAgaWYobGVuZ3RoKHguY29uZGl0aW9uKSA+IDEpewogICAgeC5jb25kaXRpb24gPC0gc2FtcGxlKHNpemU9MSwgeC5jb25kaXRpb24pCiAgfQogIAogIHRlYy5uZWlnaGJvdXIubGlzdFtbeF1dIDwtIGRhdGEuZnJhbWUoIlJlcGxpY2F0ZSI9eC5yZXAsICJDbHVzdGVyIj14LmJsb2NrLCAiQ29uZGl0aW9uIj14LmNvbmRpdGlvbiwgIk5laWdoYm91cmhvb2QiPXgpCn0KCnRlYy5uZWlnaGJvdXIubWV0YSA8LSBkby5jYWxsKHJiaW5kLmRhdGEuZnJhbWUsIHRlYy5uZWlnaGJvdXIubGlzdCkKdGVjLm5laWdoYm91ci5tZXJnZSA8LSBtZXJnZSh0ZWMubmVpZ2hib3IuZGYsIHRlYy5uZWlnaGJvdXIubWV0YSwgYnk9J05laWdoYm91cmhvb2QnKQoKdGVjLm5laWdoYm91ci5tZXJnZSREaWZmIDwtIHNpZ24odGVjLm5laWdoYm91ci5tZXJnZSRsb2dGQykKdGVjLm5laWdoYm91ci5tZXJnZSREaWZmW3RlYy5uZWlnaGJvdXIubWVyZ2UkU2lnID09IDBdIDwtIDAKYGBgCgoKYGBge3IsIGZpZy53aWR0aD05Ljc1LCBmaWcuaGVpZ2h0PTQuMTV9CmdncGxvdCh0ZWMubmVpZ2hib3VyLm1lcmdlLCBhZXMoeD1VTUFQMSwgeT1VTUFQMikpICsKICBnZW9tX3BvaW50KGRhdGE9dGVjLm5laWdoYm91ci5tZXJnZVssIGMoIlVNQVAxIiwgIlVNQVAyIildLAogICAgICAgICAgICAgY29sb3VyPSdncmV5ODAnLCBzaXplPTEpICsKICBnZW9tX3BvaW50KGRhdGE9dGVjLm5laWdoYm91ci5tZXJnZVt0ZWMubmVpZ2hib3VyLm1lcmdlJFNpZyA9PSAxLCBdLAogICAgICAgICAgICAgYWVzKGNvbG91cj1sb2dGQyksIHNpemU9NCkgKwogIHRoZW1lX2NsZWFuKCkgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudDIobG93PSJibHVlIiwgbWlkPSJncmV5ODAiLCBoaWdoPSJyZWQiKSArCiAgZmFjZXRfd3JhcCh+Q2x1c3RlcikKYGBgCgpUaGVyZSBpcyBhIHN1YnNldCBvZiB0aGUgUHJvbGlmZXJhdGluZyBURUMgdGhhdCBhcmUgZG93biwgZ29vZCwgYXMgYXJlIHRoZSBQZXJpbmF0YWwgY1RFQy4gTGlrZXdpc2UsIG1vc3Qgb2YgdGhlIEludGVydHlwaWNhbCBURUMgYXJlIHVwIGFzIHdlbGwsIGJ1dCBzb21lIGFyZSAKYWxzbyBkb3duLiBJIGRvbid0IGtub3cgaWYgdGhpcyBpcyBiZWNhdXNlIG9mIGEgY29tcG9zaXRpb25hbCBlZmZlY3Qgb3IgYmVjYXVzZSB0aGVzZSBhcmUgdGhlIG9uZXMgdGhhdCB3b3VsZCBiZSBkaWZmZXJlbnRpYXRpbmcgaW50byBtVEVDIHZpYSB0aGUgUHJvbGlmZXJhdGluZyAKVEVDIGNvbXBhcnRtZW50LgoKYGBge3J9CnRhYmxlKHRlYy5uZWlnaGJvdXIubWVyZ2UkQ2x1c3RlciwgdGVjLm5laWdoYm91ci5tZXJnZSREaWZmKQpgYGAKCkkgd291bGQgc2F5IHRoYXQgdGhlc2UgcmVzdWx0cyBtYWtlIGEgbG90IG9mIHNlbnNlLiBDYW4gSSBub3cgZXh0ZW5kIHRoaXMgdG8gaW5jbHVkZSBhbGwgdGltZSBwb2ludHMgYW5kIGZpdCBhZ2UgYXMgYSBsaW5lYXIgb3JkaW5hbCB2YXJpYWJsZT8KCiMjIEV4dGVuZGVkIFRFQyBEQSB0ZXN0aW5nIHdpdGggaz0xMQoKYGBge3J9CiMgZXhjbHVkZSB0ZWNobmljYWwgYXJ0aWZhY3QgY2x1c3Rlcgp0ZWMuc3ViLm1ldGEgPC0gdGVjLm1ldGEKdGVjLnN1Yi5tZXRhJEFnZUZhY3RvciA8LSBvcmRlcmVkKHRlYy5zdWIubWV0YSRBZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHM9YygiMXdrIiwgIjR3ayIsICIxNndrIiwgIjMyd2siLCAiNTJ3ayIpKQojIGFkZCB0aGUgbGFiZWwgYW5ub3RhdGlvbgp0ZWMuc3ViLm1ldGEkQ2x1c3RlciA8LSAiVW5rbm93biIKdGVjLnN1Yi5tZXRhJENsdXN0ZXJbdGVjLnN1Yi5tZXRhJFRGSURGLkNsdXN0ZXIgPT0gIjIiXSA8LSAiSW50ZXJ0eXBpY2FsIFRFQyIKdGVjLnN1Yi5tZXRhJENsdXN0ZXJbdGVjLnN1Yi5tZXRhJFRGSURGLkNsdXN0ZXIgPT0gIjkiXSA8LSAiUGVyaW5hdGFsIGNURUMiCnRlYy5zdWIubWV0YSRDbHVzdGVyW3RlYy5zdWIubWV0YSRURklERi5DbHVzdGVyID09ICIzIl0gPC0gIk1hdHVyZSBjVEVDIgp0ZWMuc3ViLm1ldGEkQ2x1c3Rlclt0ZWMuc3ViLm1ldGEkVEZJREYuQ2x1c3RlciA9PSAiNyJdIDwtICJNYXR1cmUgbVRFQyIKdGVjLnN1Yi5tZXRhJENsdXN0ZXJbdGVjLnN1Yi5tZXRhJFRGSURGLkNsdXN0ZXIgPT0gIjEiXSA8LSAiUG9zdC1BaXJlIG1URUMiCnRlYy5zdWIubWV0YSRDbHVzdGVyW3RlYy5zdWIubWV0YSRURklERi5DbHVzdGVyID09ICI1Il0gPC0gIlR1ZnQtbGlrZSBtVEVDIgp0ZWMuc3ViLm1ldGEkQ2x1c3Rlclt0ZWMuc3ViLm1ldGEkVEZJREYuQ2x1c3RlciA9PSAiNiJdIDwtICJQcm9saWZlcmF0aW5nIFRFQyIKdGVjLnN1Yi5tZXRhJENsdXN0ZXJbdGVjLnN1Yi5tZXRhJFRGSURGLkNsdXN0ZXIgPT0gIjgiXSA8LSAiblRFQyIKdGVjLnN1Yi5tZXRhJENsdXN0ZXJbdGVjLnN1Yi5tZXRhJFRGSURGLkNsdXN0ZXIgPT0gIjEwIl0gPC0gInNURUMiCgppbnRlci5jb2xzIDwtIGMoIiM5OTcwYWIiLCAiIzM1OTc4ZiIsICIjQjBjZGMxIiwgIiM3NjJhODMiLCAiIzAxNjY1ZSIsICIjZTdkNGU4IiwgIiNkZmMyN2QiLCAiIzhjNTEwYSIgLCIjYmY4MTJkIikKbmFtZXMoaW50ZXIuY29scykgPC0gYygiUG9zdC1BaXJlIG1URUMiLCAnSW50ZXJ0eXBpY2FsIFRFQycsICdNYXR1cmUgY1RFQycsICdUdWZ0LWxpa2UgbVRFQycsIAogICAgICAgICAgICAgICAgICAgICAgICdQcm9saWZlcmF0aW5nIFRFQycsICdNYXR1cmUgbVRFQycsICduVEVDJywgJ1BlcmluYXRhbCBjVEVDJywgJ3NURUMnKQpgYGAKCmBgYHtyfQp0ZWMuc3ViLmdleCA8LSB0ZWMuZ2V4WywgY29sbmFtZXModGVjLmdleCkgJWluJSB0ZWMuc3ViLm1ldGEkU2FtcGxlXQpgYGAKCgpJJ2xsIGJ1aWxkIGEga05OLWdyYXBoIGZyb20gdGhlIGZpcnN0IDMwIFBDcyBwcmV2aW91c2x5IGNvbXB1dGVkIG9uIGFsbCBURUMuCgpgYGB7cn0Kc2V0LnNlZWQoNDIpCnRlYy5rbm4gPC0gYnVpbGRLTk5HcmFwaCh4PWFzLm1hdHJpeCh0ZWMuc3ViLm1ldGFbLCBwYXN0ZTAoIlBDIiwgMTozMCldKSwgaz0xMSwgZD1OQSwgdHJhbnNwb3NlZD1UUlVFKQp0ZWMuZnIubGF5b3V0IDwtIGxheW91dF93aXRoX2ZyKHRlYy5rbm4pCnBsb3QodGVjLmtubiwgbGF5b3V0PXRlYy5mci5sYXlvdXQsIHZlcnRleC5mcmFtZS5jb2xvcj0nc2t5Ymx1ZScsIHZlcnRleC5jb2xvcj0nc2t5Ymx1ZScsIHZlcnRleC5sYWJlbC5jb2xvcj0nYmxhY2snLCAKICAgICB2ZXJ0ZXgubGFiZWwuZmFtaWx5PSdIZWx2ZXRpY2EnLCBlZGdlLmNvbG9yPSdncmV5NjAnLCB2ZXJ0ZXgubGFiZWwuY2V4PTAuOSwKICAgICB2ZXJ0ZXgubGFiZWwuZGlzdD0xLCBlZGdlLmFycm93LnNpemU9MC4yKQpgYGAKClRoaXMgaXMgYSBmYWlybHkgZGVuc2VseSBjb25uZWN0ZWQgbmV0d29yaywgaG93IGRvZXMgdGhlIFVNQVAgbG9vaz8KCmBgYHtyfQpzZXQuc2VlZCg0MikKdGVjLnVtYXAgPC0gdW1hcChhcy5tYXRyaXgodGVjLnN1Yi5tZXRhWywgcGFzdGUwKCJQQyIsIDE6MzApXSksCiAgICAgICAgICAgICAgICAgbl9jb21wb25lbnRzPTIsCiAgICAgICAgICAgICAgICAgbl9uZWlnaGJvcnM9MTEsIG1ldHJpYz0nZXVjbGlkZWFuJywKICAgICAgICAgICAgICAgICBpbml0PSdyYW5kb20nLCBtaW5fZGlzdD0wLjIpCnRlYy51bWFwLmRmIDwtIGFzLmRhdGEuZnJhbWUodGVjLnVtYXAkbGF5b3V0KQpjb2xuYW1lcyh0ZWMudW1hcC5kZikgPC0gYygiVU1BUDEiLCAiVU1BUDIiKQp0ZWMudW1hcC5kZiRTYW1wbGUgPC0gdGVjLnN1Yi5tZXRhJFNhbXBsZQoKdGVjLnVtYXAubWVyZ2UgPC0gbWVyZ2UodGVjLnVtYXAuZGYsIHRlYy5zdWIubWV0YSwgYnk9J1NhbXBsZScpCmBgYAoKCmBgYHtyLCBmaWcud2lkdGg9OS45NSwgZmlnLmhlaWdodD03LjE1fQpnZ3Bsb3QodGVjLnVtYXAubWVyZ2UsIGFlcyh4PVVNQVAxLCB5PVVNQVAyKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG91cj1DbHVzdGVyKSkgKwogIHRoZW1lX2NsZWFuKCkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWludGVyLmNvbHMpICsKICBmYWNldF93cmFwKH5BZ2VGYWN0b3IpICsKICBndWlkZXMoY29sb3VyPWd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXM9bGlzdChzaXplPTMpKSwKICAgICAgICAgc2hhcGU9Z3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcz1saXN0KHNpemU9MykpKQpgYGAKCgpgYGB7cn0KIyByYW5kb21seSBzZWxlY3QgdmVydGljZXMgaW4gdGhlIGdyYXBoCm4uaG9vZCA8LSAwLjEwCnRlYy5yYW5kb20udmVydGljZXMgPC0gc2FtcGxlKFYodGVjLmtubiksIHNpemU9Zmxvb3Iobi5ob29kKmxlbmd0aChWKHRlYy5rbm4pKSkpCiMgbG9vcCBvdmVyIHJhbmRvbSB2ZXJ0aWNlcyBhbmQgY291bnQgdGhlIG51bWJlciBvZiBjZWxscyBpbiBlYWNoCnRlYy52ZXJ0ZXgubGlzdCA8LSBzYXBwbHkoMTpsZW5ndGgodGVjLnJhbmRvbS52ZXJ0aWNlcyksIEZVTj1mdW5jdGlvbihYKSBuZWlnaGJvcnModGVjLmtubiwgdj10ZWMucmFuZG9tLnZlcnRpY2VzW1hdKSkKaGlzdCh1bmxpc3QobGFwcGx5KHRlYy52ZXJ0ZXgubGlzdCwgbGVuZ3RoKSksIDEwMCwgbWFpbj0iSGlzdG9ncmFtIG9mIG5laWdoYm9ycyIsIHhsYWI9Ik5laWdoYm91cmhvb2Qgc2l6ZSIpCmBgYAoKVGhpcyBpcyB0aGUgaGlzdG9ncmFtIG9mIFRFQyBuZWlnaGJvdXJob29kIHNpemVzLgoKYGBge3J9CnRlYy51bWFwLm1lcmdlJEV4cFNhbXAgPC0gcGFzdGUodGVjLnVtYXAubWVyZ2UkQWdlLCB0ZWMudW1hcC5tZXJnZSRTb3J0RGF5LCBzZXA9Il8iKQp0ZWMudW1hcC5tZXJnZSRWZXJ0ZXggPC0gYygxOm5yb3codGVjLnVtYXAubWVyZ2UpKQp0ZWMuY291bnRzIDwtIHF1YW50X25laWdoYm91cmhvb2QoZ3JhcGg9dGVjLmtubiwgbWV0YT10ZWMudW1hcC5tZXJnZSwgc2FtcGxlLmNvbHVtbj0nRXhwU2FtcCcsIHNhbXBsZS52ZXJ0aWNlcz1uLmhvb2QpCmBgYAoKCmBgYHtyfQp0ZWMucmVwcyA8LSB1bmxpc3QobGFwcGx5KHN0cnNwbGl0KHVuaXF1ZSh0ZWMudW1hcC5tZXJnZSRFeHBTYW1wKSwgc3BsaXQ9Il8iKSwgRlVOPWZ1bmN0aW9uKFgpIHBhc3RlMChYWzJdKSkpCnRlYy5jb25kIDwtIHVubGlzdChsYXBwbHkoc3Ryc3BsaXQodW5pcXVlKHRlYy51bWFwLm1lcmdlJEV4cFNhbXApLCBzcGxpdD0iXyIpLCBGVU49ZnVuY3Rpb24oWCkgcGFzdGUwKFhbMV0pKSkKCnRlYy5zYW1wbGUubWV0YSA8LSBkYXRhLmZyYW1lKCJDb25kaXRpb24iPXRlYy5jb25kLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUmVwbGljYXRlIj10ZWMucmVwcykKdGVjLnNhbXBsZS5tZXRhJFNhbXBsZSA8LSBwYXN0ZSh0ZWMuc2FtcGxlLm1ldGEkQ29uZGl0aW9uLCB0ZWMuc2FtcGxlLm1ldGEkUmVwbGljYXRlLCBzZXA9Il8iKQpyb3duYW1lcyh0ZWMuc2FtcGxlLm1ldGEpIDwtIHRlYy5zYW1wbGUubWV0YSRTYW1wbGUKdGVjLnNhbXBsZS5tZXRhJENvbmRpdGlvbiA8LSBvcmRlcmVkKHRlYy5zYW1wbGUubWV0YSRDb25kaXRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHM9YygiMXdrIiwgIjR3ayIsICIxNndrIiwgIjMyd2siLCAiNTJ3ayIpKQp0ZWMubW9kZWwgPC0gbW9kZWwubWF0cml4KH4gQ29uZGl0aW9uLCBkYXRhPXRlYy5zYW1wbGUubWV0YSkKaGVhZCh0ZWMubW9kZWwpCmBgYAoKYGBge3J9CnRlYy5kZ2UgPC0gREdFTGlzdCh0ZWMuY291bnRzWywgcm93bmFtZXModGVjLm1vZGVsKV0sIGxpYi5zaXplPWxvZyhjb2xTdW1zKHRlYy5jb3VudHNbLCByb3duYW1lcyh0ZWMubW9kZWwpXSkpKQp0ZWMuZGdlIDwtIGVzdGltYXRlRGlzcCh0ZWMuZGdlLCB0ZWMubW9kZWwpCnRlYy5maXQgPC0gZ2xtUUxGaXQodGVjLmRnZSwgdGVjLm1vZGVsLCByb2J1c3Q9VFJVRSkKIyB0ZWMuY29udHJhc3QgPC0gbWFrZUNvbnRyYXN0cyhDb25kaXRpb25BIC0gQ29uZGl0aW9uQiwgbGV2ZWxzPXRlYy5tb2RlbCkKIyB0ZWMucmVzIDwtIGdsbVFMRlRlc3QodGVjLmZpdCwgY29udHJhc3Q9dGVjLmNvbnRyYXN0KQoKdGVjLnJlcyA8LSBhcy5kYXRhLmZyYW1lKHRvcFRhZ3MoZ2xtUUxGVGVzdCh0ZWMuZml0LCBjb2VmPTIpLCBzb3J0LmJ5PSdub25lJywgbj1JbmYpKQp0ZWMucmVzJFNpZyA8LSBhcy5mYWN0b3IoYXMubnVtZXJpYyh0ZWMucmVzJEZEUiA8PSAwLjA1KSkKdGVjLnJlcyROZWlnaGJvdXJob29kIDwtIGFzLm51bWVyaWMocm93bmFtZXModGVjLnJlcykpCgojIGNvbnRyb2wgdGhlIHNwYXRpYWwgRkRSCnF2YWxzIDwtIHRlYy5yZXMkUFZhbHVlCmlzLnNpZyA8LSBxdmFscyA8PSAwLjA1CnN1bW1hcnkoaXMuc2lnKQpgYGAKClRoZXJlIGFyZSA4NSBEQSBuZWlnaGJvdXJob29kcyAtIEkgZXhwZWN0IHRoZXNlIHNob3VsZCByZWZsZWN0IHRoZSBQZXJpbmF0YWwsIEludGVydHlwaWNhbCwgUHJvbGlmZXJhdGluZyBhbmQgc1RFQy4gSSdsbCB1c2UgdGhlIGRpc3RhbmNlLWJhc2VkIGFwcHJvYWNoIHRvIGNvcnJlY3QgZm9yIAp0aGUgc3BhdGlhbCBGRFIuCgpgYGB7cn0KdGVjLnNwYXRpYWxmZHIgPC0gZ3JhcGhfc3BhdGlhbEZEUihuZWlnaGJvcmhvb2RzPXRlYy52ZXJ0ZXgubGlzdCwgZ3JhcGg9dGVjLmtubiwgY29ubmVjdGl2aXR5PSJkaXN0YW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGNhPWFzLm1hdHJpeCh0ZWMuc3ViLm1ldGFbLCBwYXN0ZTAoIlBDIiwgMTozMCldKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwdmFsdWVzPXRlYy5yZXNbb3JkZXIodGVjLnJlcyROZWlnaGJvdXJob29kKSwgXSRQVmFsdWUpCnRlYy5yZXMkU3BhdGlhbEZEUltvcmRlcih0ZWMucmVzJE5laWdoYm91cmhvb2QpXSA8LSB0ZWMuc3BhdGlhbGZkcgpxdmFscyA8LSB0ZWMuc3BhdGlhbGZkcgppcy5zaWcgPC0gcXZhbHMgPD0gMC4wNQpzdW1tYXJ5KGlzLnNpZykKYGBgCgpJbnRlcmVzdGluZywgdGhlcmUgYXJlIDI2IG5laWdoYm91cmhvb2RzIG5vIGxvbmdlciBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IGFmdGVyIHRoZSBzcGF0aWFsIEZEUiBjb3JyZWN0aW9uIC0gaG9wZWZ1bGx5IHRoZXNlIGFyZSBnZW51aW5lbHkgZmFsc2UtcG9zaXRpdmVzLgoKSW4gZWFjaCBuZWlnaGJvdXJob29kLCB3aGF0IGlzIHRoZSBtb3N0IGNvbW1vbiBjb25kaXRpb24gb3IgYmxvY2sgb2YgY2VsbHM/CgpgYGB7cn0KdGVjLm5laWdoYm91ci5leHBycyA8LSBuZWlnaGJvcmhvb2RfZXhwcmVzc2lvbih0ZWMudmVydGV4Lmxpc3QsIHRlYy5zdWIuZ2V4KQpgYGAKCkVtYmVkIHRoZXNlIGh5cGVyc3BoZXJlcyB3aXRoIGEgUENBIGFuZCBVTUFQLgoKYGBge3J9CnRlYy5uZWlnaGJvdXIucGNhIDwtIHByY29tcCgodCh0ZWMubmVpZ2hib3VyLmV4cHJzW3RlYy5odmdzJEhWRywgXSkpKQpwYWlycyh0ZWMubmVpZ2hib3VyLnBjYSR4WywgYygxOjUpXSkKYGBgCgpgYGB7cn0Kc2V0LnNlZWQoNDIpCm5laWdoYm91cmhvb2QudW1hcCA8LSB1bWFwKHRlYy5uZWlnaGJvdXIucGNhJHhbLCBjKDE6MzApXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jb21wb25lbnRzPTIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fbmVpZ2hib3JzPTExLCBtZXRyaWM9J2V1Y2xpZGVhbicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGluaXQ9J3JhbmRvbScsIG1pbl9kaXN0PTAuMSkKYGBgCgpXZSBjYW4gb3ZlcmxheSB0aGUgREEgdGVzdGluZyBvbiB0aGVzZSBuZWlnaGJvdXJob29kcy4KCmBgYHtyfQp0ZWMubmVpZ2hib3IuZGYgPC0gdGVjLnJlc1ssIGMoImxvZ0ZDIiwgIk5laWdoYm91cmhvb2QiLCAiU3BhdGlhbEZEUiIpXQp0ZWMubmVpZ2hib3IuZGYgPC0gZG8uY2FsbChjYmluZC5kYXRhLmZyYW1lLCBsaXN0KHRlYy5uZWlnaGJvci5kZiwgYXMuZGF0YS5mcmFtZShuZWlnaGJvdXJob29kLnVtYXAkbGF5b3V0KSkpCmNvbG5hbWVzKHRlYy5uZWlnaGJvci5kZikgPC0gYygibG9nRkMiLCAiTmVpZ2hib3VyaG9vZCIsICJTcGF0aWFsRkRSIiwgIlVNQVAxIiwgIlVNQVAyIikKdGVjLm5laWdoYm9yLmRmJFNpZyA8LSBhcy5udW1lcmljKHRlYy5uZWlnaGJvci5kZiRTcGF0aWFsRkRSIDw9IDAuMDUpCgpnZ3Bsb3QodGVjLm5laWdoYm9yLmRmLCBhZXMoeD1VTUFQMSwgeT1VTUFQMikpICsKICBnZW9tX3BvaW50KGRhdGE9dGVjLm5laWdoYm9yLmRmW3RlYy5uZWlnaGJvci5kZiRTaWcgPT0gMCwgXSwKICAgICAgICAgICAgIGNvbG91cj0nZ3JleTgwJywgc2l6ZT0yKSArCiAgZ2VvbV9wb2ludChkYXRhPXRlYy5uZWlnaGJvci5kZlt0ZWMubmVpZ2hib3IuZGYkU2lnID09IDEsIF0sCiAgICAgICAgICAgICBhZXMoY29sb3VyPWxvZ0ZDKSwgc2l6ZT00KSArCiAgdGhlbWVfY2xlYW4oKSArCiAgc2NhbGVfY29sb3VyX2dyYWRpZW50Mihsb3c9ImJsdWUiLCBtaWQ9ImdyZXk4MCIsIGhpZ2g9InJlZCIpCmBgYAoKU29tZSBhcmUgdXAgYW5kIHNvbWUgYXJlIGRvd24uIFdoaWNoIFRFQyBzdWJ0eXBlcyBkbyB0aGV5IGxhcmdlbHkgY29ycmVzcG9uZCB3aXRoPyBUaGF0IGJpZyBzdHJlYWsgb2YgbG93ZXIgYWJ1bmRhbmNlIG5laWdoYm91cmhvb2RzIHNob3VsZCBiZSB0aGUgZGlmZmVyZW50aWF0aW9uIAp0cmFqZWN0b3J5IGZyb20gSW50ZXJ0eXBpY2FsIHRvIE1hdHVyZSBtVEVDLgoKYGBge3J9CnRlYy5uZWlnaGJvdXIubGlzdCA8LSBsaXN0KCkKZm9yKHggaW4gc2VxX2Fsb25nKDE6bGVuZ3RoKHRlYy52ZXJ0ZXgubGlzdCkpKXsKICB4LmRmIDwtIHRlYy51bWFwLm1lcmdlW3RlYy51bWFwLm1lcmdlJFZlcnRleCAlaW4lIHRlYy52ZXJ0ZXgubGlzdFtbeF1dLCBdCiAgeC5yZXAgPC0gbmFtZXModGFibGUoeC5kZiRTb3J0RGF5KSlbd2hpY2godGFibGUoeC5kZiRTb3J0RGF5KSA9PSBtYXgodGFibGUoeC5kZiRTb3J0RGF5KSkpXQogIGlmKGxlbmd0aCh4LnJlcCkgPiAxKXsKICAgIHgucmVwIDwtIHNhbXBsZShzaXplPTEsIHgucmVwKQogIH0KICB4LmJsb2NrIDwtIG5hbWVzKHRhYmxlKHguZGYkQ2x1c3RlcikpW3doaWNoKHRhYmxlKHguZGYkQ2x1c3RlcikgPT0gbWF4KHRhYmxlKHguZGYkQ2x1c3RlcikpKV0KICAgIGlmKGxlbmd0aCh4LmJsb2NrKSA+IDEpewogICAgeC5ibG9jayA8LSBzYW1wbGUoc2l6ZT0xLCB4LmJsb2NrKQogIH0KICB4LmNvbmRpdGlvbiA8LSBuYW1lcyh0YWJsZSh4LmRmJEFnZSkpW3doaWNoKHRhYmxlKHguZGYkQWdlKSA9PSBtYXgodGFibGUoeC5kZiRBZ2UpKSldCiAgICBpZihsZW5ndGgoeC5jb25kaXRpb24pID4gMSl7CiAgICB4LmNvbmRpdGlvbiA8LSBzYW1wbGUoc2l6ZT0xLCB4LmNvbmRpdGlvbikKICB9CiAgCiAgdGVjLm5laWdoYm91ci5saXN0W1t4XV0gPC0gZGF0YS5mcmFtZSgiUmVwbGljYXRlIj14LnJlcCwgIkNsdXN0ZXIiPXguYmxvY2ssICJDb25kaXRpb24iPXguY29uZGl0aW9uLCAiTmVpZ2hib3VyaG9vZCI9eCkKfQoKdGVjLm5laWdoYm91ci5tZXRhIDwtIGRvLmNhbGwocmJpbmQuZGF0YS5mcmFtZSwgdGVjLm5laWdoYm91ci5saXN0KQp0ZWMubmVpZ2hib3VyLm1lcmdlIDwtIG1lcmdlKHRlYy5uZWlnaGJvci5kZiwgdGVjLm5laWdoYm91ci5tZXRhLCBieT0nTmVpZ2hib3VyaG9vZCcpCgp0ZWMubmVpZ2hib3VyLm1lcmdlJERpZmYgPC0gc2lnbih0ZWMubmVpZ2hib3VyLm1lcmdlJGxvZ0ZDKQp0ZWMubmVpZ2hib3VyLm1lcmdlJERpZmZbdGVjLm5laWdoYm91ci5tZXJnZSRTaWcgPT0gMF0gPC0gMApgYGAKCgpgYGB7ciwgZmlnLndpZHRoPTkuNzUsIGZpZy5oZWlnaHQ9Ni4xNX0KZ2dwbG90KHRlYy5uZWlnaGJvdXIubWVyZ2UsIGFlcyh4PVVNQVAxLCB5PVVNQVAyKSkgKwogIGdlb21fcG9pbnQoZGF0YT10ZWMubmVpZ2hib3VyLm1lcmdlWywgYygiVU1BUDEiLCAiVU1BUDIiKV0sCiAgICAgICAgICAgICBjb2xvdXI9J2dyZXk4MCcsIHNpemU9MSkgKwogIGdlb21fcG9pbnQoZGF0YT10ZWMubmVpZ2hib3VyLm1lcmdlW3RlYy5uZWlnaGJvdXIubWVyZ2UkU2lnID09IDEsIF0sCiAgICAgICAgICAgICBhZXMoY29sb3VyPWxvZ0ZDKSwgc2l6ZT00KSArCiAgdGhlbWVfY2xlYW4oKSArCiAgc2NhbGVfY29sb3VyX2dyYWRpZW50Mihsb3c9ImJsdWUiLCBtaWQ9ImdyZXk4MCIsIGhpZ2g9InJlZCIpICsKICBmYWNldF93cmFwKH5DbHVzdGVyKQpgYGAKClRoaXMgZXh0ZW5kZWQgYW5hbHlzaXMgd2l0aCBrPTExIGFsc28gZGV0ZWN0cyB0aGUgaW5jcmVhc2UgaW4gdGhlIHNtYWxsIHNURUMgcG9wdWxhdGlvbi4gVGhlcmUgYWxzbyBhcHBlYXJzIHRvIGJlIG1vcmUgaGV0ZXJvZ2VuZWl0eSBpbiB0aGUgSW50ZXJ0eXBpY2FsIFRFQywgYW5kLCAKc29tZXdoYXQgaW5ndHJpZ3VpbmdseSwgY2hhbmdlcyBhbW9uZ3N0IHRoZSBtYXR1cmUgbVRFQyB3aGljaCB3ZXJlIG5vdCBkZXRlY3RlZCBvcmlnaW5hbGx5LgoKYGBge3J9CnRhYmxlKHRlYy5uZWlnaGJvdXIubWVyZ2UkQ2x1c3RlciwgdGVjLm5laWdoYm91ci5tZXJnZSREaWZmKQpgYGAKCkkgd291bGQgc2F5IHRoYXQgdGhlc2UgcmVzdWx0cyBtYWtlIGEgbG90IG9mIHNlbnNlLiBJJ2xsIGV4dGVuZCBpdCB0byBpbmNsdWRlIHRoZSBxdWFkcmF0aWMgdGVzdGluZyB3aGljaCBzaG91bGQgcGljayB1cCB0aGUgaW52ZXJzZS1wYXJhYm9saWMgcHJvZmlsZSBvZiAKdGhlIFBvc3QtQWlyZSBtVEVDIHBvcHVsYXRpb24uCgpgYGB7cn0KcXVhZC50ZWMucmVzIDwtIGFzLmRhdGEuZnJhbWUodG9wVGFncyhnbG1RTEZUZXN0KHRlYy5maXQsIGNvZWY9MyksIHNvcnQuYnk9J25vbmUnLCBuPUluZikpCnF1YWQudGVjLnJlcyRTaWcgPC0gYXMuZmFjdG9yKGFzLm51bWVyaWMocXVhZC50ZWMucmVzJEZEUiA8PSAwLjA1KSkKcXVhZC50ZWMucmVzJE5laWdoYm91cmhvb2QgPC0gYXMubnVtZXJpYyhyb3duYW1lcyhxdWFkLnRlYy5yZXMpKQoKIyBjb250cm9sIHRoZSBzcGF0aWFsIEZEUgpxdmFscyA8LSBxdWFkLnRlYy5yZXMkUFZhbHVlCmlzLnNpZyA8LSBxdmFscyA8PSAwLjA1CnN1bW1hcnkoaXMuc2lnKQpgYGAKClRoZXJlIGFyZSAyNyBEQSBuZWlnaGJvdXJob29kcyBmcm9tIHRoZSBxdWFkcmF0aWMgbW9kZWwgLSBJIGV4cGVjdCB0aGVzZSBzaG91bGQgcmVmbGVjdCB0aGUgUG9zdC1BaXJlIG1URUMuIAoKYGBge3J9CnF1YWQudGVjLnNwYXRpYWxmZHIgPC0gZ3JhcGhfc3BhdGlhbEZEUihuZWlnaGJvcmhvb2RzPXRlYy52ZXJ0ZXgubGlzdCwgZ3JhcGg9dGVjLmtubiwgY29ubmVjdGl2aXR5PSJkaXN0YW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGNhPWFzLm1hdHJpeCh0ZWMuc3ViLm1ldGFbLCBwYXN0ZTAoIlBDIiwgMTozMCldKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwdmFsdWVzPXF1YWQudGVjLnJlc1tvcmRlcihxdWFkLnRlYy5yZXMkTmVpZ2hib3VyaG9vZCksIF0kUFZhbHVlKQpxdWFkLnRlYy5yZXMkU3BhdGlhbEZEUltvcmRlcihxdWFkLnRlYy5yZXMkTmVpZ2hib3VyaG9vZCldIDwtIHF1YWQudGVjLnNwYXRpYWxmZHIKcXZhbHMgPC0gcXVhZC50ZWMuc3BhdGlhbGZkcgppcy5zaWcgPC0gcXZhbHMgPD0gMC4wNQpzdW1tYXJ5KGlzLnNpZykKYGBgCgpEYW5nLCBjbGVhcmx5IHN0aWxsIHRoZSBzbWFsbCBncm91cCBvZiBQb3N0LUFpcmUgbVRFQyBtZWFucyB0aGlzIGlzbid0IHN1ZmZpY2llbnRseSBzZW5zaXRpdmUgYWZ0ZXIgbXVsdGlwbGUtdGVzdGluZyBjb3JyZWN0aW9uLgoKYGBge3IsIGZpZy53aWR0aD05Ljc1LCBmaWcuaGVpZ2h0PTYuMTV9CmdncGxvdCh0ZWMubmVpZ2hib3VyLm1lcmdlLCBhZXMoeD1VTUFQMSwgeT1VTUFQMikpICsKICBnZW9tX3BvaW50KGRhdGE9dGVjLm5laWdoYm91ci5tZXJnZVssIGMoIlVNQVAxIiwgIlVNQVAyIildLAogICAgICAgICAgICAgY29sb3VyPSdncmV5ODAnLCBzaXplPTEpICsKICBnZW9tX3BvaW50KGRhdGE9dGVjLm5laWdoYm91ci5tZXJnZSwKICAgICAgICAgICAgIGFlcyhjb2xvdXI9Q2x1c3RlciksIHNpemU9MykgKwogIHRoZW1lX2NsZWFuKCkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWludGVyLmNvbHMpICsKICBmYWNldF93cmFwKH5DbHVzdGVyKQpgYGAKCkhtbSwgdGhlIFBvc3QtQWlyZSBtVEVDIGRvbid0IGZvcm0gYSBzaW5nbGUgbmVpZ2hib3VyaG9vZCBvbiB0aGVpciBvd24gYnV0IHJhdGhlciAzIHNlcGFyYXRlIG9uZXMuIEkgd291bGQgc2F5IHRoaXMgcG9zc2libHkgdG9vIGdyYW51bGFyLgoKIyMgQ29tcG9zaXRpb25hbCBlZmZlY3RzCgpGaXJzdGx5LCBhcmUgY29tcG9zaXRpb25hbCBlZmZlY3RzIGEgcHJvYmxlbSBoZXJlLCBhbmQgc2Vjb25kbHksIGRvZXMgdGhlIHJlZmluZWQgc2FtcGxpbmcgc2NoZW1lIGhhbmRsZSB0aGlzPyBJJ2xsIHNldCB1cCBhIG5ldyBzaW11bGF0aW9uIHRoYXQgaGFzIDIgCmNsdXN0ZXJzLCBvbmx5IG9uZSBvZiB3aGljaCBjb250YWlucyBkaWZmZXJlbnRpYWxseSBhYnVuZGFudCBuZWlnaGJvdXJob29kcy4KCmBgYHtyLCBlY2hvPVRSVUUsIHdhcm5pbmc9RkFMU0V9CnNldC5zZWVkKDQyKQpyLm4gPC0gMTAwMApuLmRpbSA8LSA1MApibG9jazEuY2VsbHMgPC0gMjUwCiMgc2VsZWN0IGEgc2V0IG9mIGVpZ2VuIHZhbHVlcyBmb3IgdGhlIGNvdmFyaWFuY2UgbWF0cml4IG9mIGVhY2ggYmxvY2ssIHNheSA1MCBlaWdlbnZhbHVlcz8KYmxvY2sxLmVpZ2VucyA8LSBzYXBwbHkoMTpuLmRpbSwgRlVOPWZ1bmN0aW9uKFgpIHJleHAobj0xLCByYXRlPWFicyhydW5pZihuPTEsIG1pbj0wLCBtYXg9NTApKSkpCmJsb2NrMS5laWdlbnMgPC0gYmxvY2sxLmVpZ2Vuc1tvcmRlcihibG9jazEuZWlnZW5zKV0KYmxvY2sxLnAgPC0gcXIuUShxcihtYXRyaXgocm5vcm0oYmxvY2sxLmNlbGxzXjIsIG1lYW49NCwgc2Q9MC4wMSksIGJsb2NrMS5jZWxscykpKQpibG9jazEuc2lnbWEgPC0gY3Jvc3Nwcm9kKGJsb2NrMS5wLCBibG9jazEucCpibG9jazEuZWlnZW5zKQpibG9jazEuZ2V4IDwtIGFicyhybXZub3JtKG49ci5uLCBtZWFuPXJub3JtKG49YmxvY2sxLmNlbGxzLCBtZWFuPTIsIHNkPTAuMDEpLCBzaWdtYT1ibG9jazEuc2lnbWEpKQoKCmJsb2NrMy5jZWxscyA8LSAyNTAKIyBzZWxlY3QgYSBzZXQgb2YgZWlnZW4gdmFsdWVzIGZvciB0aGUgY292YXJpYW5jZSBtYXRyaXggb2YgZWFjaCBibG9jaywgc2F5IDUwIGVpZ2VudmFsdWVzPwpibG9jazMuZWlnZW5zIDwtIHNhcHBseSgxOm4uZGltLCBGVU49ZnVuY3Rpb24oWCkgcmV4cChuPTEsIHJhdGU9YWJzKHJ1bmlmKG49MSwgbWluPTAsIG1heD01MCkpKSkKYmxvY2szLmVpZ2VucyA8LSBibG9jazMuZWlnZW5zW29yZGVyKGJsb2NrMy5laWdlbnMpXQpibG9jazMucCA8LSBxci5RKHFyKG1hdHJpeChybm9ybShibG9jazMuY2VsbHNeMiwgbWVhbj00LCBzZD0wLjAxKSwgYmxvY2szLmNlbGxzKSkpCmJsb2NrMy5zaWdtYSA8LSBjcm9zc3Byb2QoYmxvY2szLnAsIGJsb2NrMy5wKmJsb2NrMy5laWdlbnMpCmJsb2NrMy5nZXggPC0gYWJzKHJtdm5vcm0obj1yLm4sIG1lYW49cm5vcm0obj1ibG9jazMuY2VsbHMsIG1lYW49NSwgc2Q9MC4wMSksIHNpZ21hPWJsb2NrMy5zaWdtYSkpCgpzaW0yLmdleCA8LSBkby5jYWxsKGNiaW5kLCBsaXN0KCJiMSI9YmxvY2sxLmdleCwgImIzIj1ibG9jazMuZ2V4KSkKYGBgCgoKYGBge3J9CnNpbTIucGNhIDwtIHByY29tcF9pcmxiYSh0KHNpbTIuZ2V4KSwgbj01MCwgc2NhbGUuPVRSVUUsIGNlbnRlcj1UUlVFKQpwYWlycyhzaW0yLnBjYSR4WywgYygxOjUpXSkKYGBgCgpJJ2xsIHVzZSB0aGUgcmVkdWNlZCBkaW1lbnNpb25zIGhlcmUgdG8gY29tcHV0ZSBhIEtOTi1ncmFwaCBhbmQgdmlzdWFsaXNlIGl0IHVzaW5nIGEgRnJ1Y3Rlcm1hbi1SZWluZ29sZCBsYXlvdXQuCgpgYGB7cn0Kc2V0LnNlZWQoNDIpCnNpbTIua25uIDwtIGJ1aWxkS05OR3JhcGgoeD1zaW0yLnBjYSR4WywgYygxOjMwKV0sIGs9MjEsIGQ9TkEsIHRyYW5zcG9zZWQ9VFJVRSkKc2ltMi5mci5sYXlvdXQgPC0gbGF5b3V0X3dpdGhfZnIoc2ltMi5rbm4pCnBsb3Qoc2ltMi5rbm4sIGxheW91dD1zaW0yLmZyLmxheW91dCwgdmVydGV4LmZyYW1lLmNvbG9yPSdza3libHVlJywgdmVydGV4LmNvbG9yPSdza3libHVlJywgdmVydGV4LmxhYmVsLmNvbG9yPSdibGFjaycsIAogICAgIHZlcnRleC5sYWJlbC5mYW1pbHk9J0hlbHZldGljYScsIGVkZ2UuY29sb3I9J2dyZXk2MCcsIHZlcnRleC5sYWJlbC5jZXg9MC45LAogICAgIHZlcnRleC5sYWJlbC5kaXN0PTEsIGVkZ2UuYXJyb3cuc2l6ZT0wLjIpCmBgYAoKQWxzbyBhIFVNQVAgbGF5b3V0LgoKYGBge3J9CnNldC5zZWVkKDQyKQpzdGVtLnRhLnVtYXAgPC0gdW1hcChzaW0yLnBjYSR4WywgYygxOjMwKV0sCiAgICAgICAgICAgICAgICAgICAgIG5fY29tcG9uZW50cz0yLAogICAgICAgICAgICAgICAgICAgICBuX25laWdoYm9ycz0yMSwgbWV0cmljPSdldWNsaWRlYW4nLAogICAgICAgICAgICAgICAgICAgICBpbml0PSdyYW5kb20nLCBtaW5fZGlzdD0wLjEpCnBsb3Qoc3RlbS50YS51bWFwJGxheW91dCwgY29sPWMocmVwKCJyZWQiLCBibG9jazEuY2VsbHMpLCByZXAoIm9yYW5nZSIsIGJsb2NrMy5jZWxscykpLAogICAgIHhsYWI9IlVNQVAgMSIsIHlsYWI9IlVNQVAgMiIpCmBgYAoKV2l0aGluIGVhY2ggb2YgdGhlc2UgY2xvdWRzIG9mIHBvaW50cyBJIHdpbGwgcmFuZG9tbHkgbGFiZWwgMTo5IGluIGJsb2NrIDEgYW5kIDE6MSBpbiBibG9jayAyLgoKYGBge3J9CnNldC5zZWVkKDQyKQpibG9jazEuY29uZCA8LSByZXAoIkEiLCBibG9jazEuY2VsbHMpCmJsb2NrMS5hIDwtIHNhbXBsZSgxOmJsb2NrMS5jZWxscywgc2l6ZT1mbG9vcihibG9jazEuY2VsbHMqMC4xKSkKYmxvY2sxLmIgPC0gc2V0ZGlmZigxOmJsb2NrMS5jZWxscywgYmxvY2sxLmEpCmJsb2NrMS5jb25kW2Jsb2NrMS5iXSA8LSAiQiIKCmJsb2NrMy5jb25kIDwtIHJlcCgiQSIsIGJsb2NrMy5jZWxscykKYmxvY2szLmEgPC0gc2FtcGxlKDE6YmxvY2szLmNlbGxzLCBzaXplPWZsb29yKGJsb2NrMy5jZWxscyowLjUpKQpibG9jazMuYiA8LSBzZXRkaWZmKDE6YmxvY2szLmNlbGxzLCBibG9jazMuYSkKYmxvY2szLmNvbmRbYmxvY2szLmJdIDwtICJCIgoKbWV0YS5kZiA8LSBkYXRhLmZyYW1lKCJCbG9jayI9YyhyZXAoIkIxIiwgYmxvY2sxLmNlbGxzKSwgcmVwKCJCMyIsIGJsb2NrMy5jZWxscykpLAogICAgICAgICAgICAgICAgICAgICAgIkNvbmRpdGlvbiI9YyhibG9jazEuY29uZCwgYmxvY2szLmNvbmQpLAogICAgICAgICAgICAgICAgICAgICAgIlJlcGxpY2F0ZSI9YyhyZXAoIlIxIiwgZmxvb3IoYmxvY2sxLmNlbGxzKjAuMzMpKSwgcmVwKCJSMiIsIGZsb29yKGJsb2NrMS5jZWxscyowLjMzKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoIlIzIiwgYmxvY2sxLmNlbGxzLSgyKmZsb29yKGJsb2NrMS5jZWxscyowLjMzKSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoIlIxIiwgZmxvb3IoYmxvY2szLmNlbGxzKjAuMzMpKSwgcmVwKCJSMiIsIGZsb29yKGJsb2NrMy5jZWxscyowLjMzKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoIlIzIiwgYmxvY2szLmNlbGxzLSgyKmZsb29yKGJsb2NrMy5jZWxscyowLjMzKSkpKSkKbWV0YS5kZiA8LSBjYmluZChtZXRhLmRmLCBzdGVtLnRhLnVtYXAkbGF5b3V0KQpjb2xuYW1lcyhtZXRhLmRmKSA8LSBjKCJCbG9jayIsICJDb25kaXRpb24iLCAiUmVwbGljYXRlIiwgIlVNQVAxIiwgIlVNQVAyIikKIyBkZWZpbmUgYSAic2FtcGxlIiBhcyB0ZWggY29tYmluYXRpb24gb2YgY29uZGl0aW9uIGFuZCByZXBsaWNhdGUKbWV0YS5kZiRTYW1wbGUgPC0gcGFzdGUobWV0YS5kZiRDb25kaXRpb24sIG1ldGEuZGYkUmVwbGljYXRlLCBzZXA9Il8iKQptZXRhLmRmJFZlcnRleCA8LSBjKDE6bnJvdyhtZXRhLmRmKSkKYGBgCgoKYGBge3J9CmdncGxvdChtZXRhLmRmLCBhZXMoeD1VTUFQMSwgeT1VTUFQMikpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvdXI9QmxvY2ssIHNoYXBlPVJlcGxpY2F0ZSkpICsKICB0aGVtZV9jbGVhbigpICsKICBzY2FsZV9jb2xvdXJfbnBnKCkgKwogIGZhY2V0X3dyYXAofkNvbmRpdGlvbikgKwogIGd1aWRlcyhjb2xvdXI9Z3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcz1saXN0KHNpemU9MykpLAogICAgICAgICBzaGFwZT1ndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzPWxpc3Qoc2l6ZT0zKSkpCmBgYAoKCgpgYGB7ciwgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0Kc2V0LnNlZWQoNDIpCnJlZmluZV92ZXJ0ZXggPC0gZnVuY3Rpb24odmVydGV4Lmtubiwgdi5peCwgWF9wY2EpewogICMgdmVydGV4LmtubjogS05OIGdyYXBoIGZvciByYW5kb21seSBzYW1wbGVkIHBvaW50cyAob3V0cHV0IG9mIEJpb2NOZWlnaGJvcnM6OmZpbmRLTk4pCiAgIyB2Lml4OiBpbmRleCBvZiB2ZXJ0ZXggdG8gcmVmaW5lIGluIHZlcnRleC5rbm4KICAKICAjIyBDYWxjdWxhdGUgbWVkaWFuIHByb2ZpbGUgb2YgS05OcyBvZiB2ZXJ0ZXgKICB2Lm1lZCA8LSBhcHBseShYX3BjYVt2ZXJ0ZXgua25uJGluZGV4W3YuaXgsXSxdLCAyLCBtZWRpYW4pCiAgIyMgRmluZCB0aGUgY2xvc2VzdCBwb2ludCB0byB0aGUgbWVkaWFuIGFuZCBzYW1wbGUKICByZWZpbmVkLnZlcnRleCA8LSBCaW9jTmVpZ2hib3JzOjpmaW5kS05OKHJiaW5kKHYubWVkLCBYX3BjYSksIHN1YnNldD0xLCBrPTEpW1siaW5kZXgiXV1bMV0gLSAxICMjIC0xIHRvIHJlbW92ZSB0aGUgbWVkaWFuCiAgcmV0dXJuKHJlZmluZWQudmVydGV4KQp9CgpncmFwaCA8LSBzaW0yLmtubgpzYW1wbGUudmVydGljZXMgPC0gMC4xClhfcGNhIDwtIHNpbTIucGNhJHhbLCBjKDE6MzApXQoKcmFuZG9tLnZlcnRpY2VzIDwtIHNhbXBsZShWKGdyYXBoKSwgc2l6ZT1mbG9vcihzYW1wbGUudmVydGljZXMqbGVuZ3RoKFYoZ3JhcGgpKSkpCnZlcnRleC5rbm4gPC0gQmlvY05laWdoYm9yczo6ZmluZEtOTihYPVhfcGNhLCBrPTIxLCBzdWJzZXQ9YXMudmVjdG9yKHJhbmRvbS52ZXJ0aWNlcykpCnJlZmluZWQudmVydGljZXMgPC0gVihncmFwaClbc2FwcGx5KDE6bnJvdyh2ZXJ0ZXgua25uJGluZGV4KSwgZnVuY3Rpb24oaSkgcmVmaW5lX3ZlcnRleCh2ZXJ0ZXgua25uLCBpLCBYX3BjYSkpXQoKdmVydGV4Lmxpc3QgPC0gc2FwcGx5KDE6bGVuZ3RoKHJhbmRvbS52ZXJ0aWNlcyksIEZVTj1mdW5jdGlvbihYKSBuZWlnaGJvcnMoZ3JhcGgsIHY9cmFuZG9tLnZlcnRpY2VzW1hdKSkKdmVydGV4Lmxpc3QucmVmaW5lZCA8LSBzYXBwbHkoMTpsZW5ndGgocmVmaW5lZC52ZXJ0aWNlcyksIEZVTj1mdW5jdGlvbihYKSBuZWlnaGJvcnMoZ3JhcGgsIHY9cmVmaW5lZC52ZXJ0aWNlc1tYXSkpCgpwbG90KGhpc3QodW5saXN0KGxhcHBseSh2ZXJ0ZXgubGlzdC5yZWZpbmVkLCBsZW5ndGgpKSwgMTAwLCBwbG90PUZBTFNFKSwgY29sPSdncmVlbicsIG1haW49Ikhpc3RvZ3JhbSBvZiBuZWlnaGJvcnMiLCB4bGFiPSJOZWlnaGJvdXJob29kIHNpemUiKQpwbG90KGhpc3QodW5saXN0KGxhcHBseSh2ZXJ0ZXgubGlzdCwgbGVuZ3RoKSksIDEwMCwgcGxvdD1GQUxTRSksICBjb2w9J2JsdWUnLCBhZGQ9VFJVRSkKYGBgCgpUaGUgcmVmaW5lZCBzYW1wbGluZyBzY2hlbWUgbGVhZHMgdG8gbGFyZ2UgbmVpZ2hib3VyaG9vZHMgb3ZlcmFsbCAtIHdlIHRoaW5rIHRoaXMgbWlnaHQgaW5jcmVhc2UgcG93ZXIgYW5kIHNlbnNpdGl2aXR5IGFzIHRoZSBjb3VudHMgaW4gZWFjaCB3aWxsIGFsc28gYmUgCmxhcmdlciBhbmQgdGhlcmVmb3JlIG1vcmUgc3RhYmxlLgoKIyMjIFRlc3QgdXNpbmcgcmFuZG9tIHNhbXBsaW5nCgpgYGB7cn0Kc2ltMi5jb3VudHMgPC0gcXVhbnRfbmVpZ2hib3VyaG9vZChncmFwaD1zaW0yLmtubiwgbWV0YT1tZXRhLmRmLCBzYW1wbGUuY29sdW1uPSdTYW1wbGUnLCBzYW1wbGUudmVydGljZXM9bi5ob29kKQpzYW1wbGUubWV0YSA8LSBkYXRhLmZyYW1lKCJDb25kaXRpb24iPWMocmVwKCJBIiwgMyksIHJlcCgiQiIsIDMpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAiUmVwbGljYXRlIj1yZXAoYygiUjEiLCAiUjIiLCAiUjMiKSwgMikpCnNhbXBsZS5tZXRhJFNhbXBsZSA8LSBwYXN0ZShzYW1wbGUubWV0YSRDb25kaXRpb24sIHNhbXBsZS5tZXRhJFJlcGxpY2F0ZSwgc2VwPSJfIikKcm93bmFtZXMoc2FtcGxlLm1ldGEpIDwtIHNhbXBsZS5tZXRhJFNhbXBsZQojIHNpbTIubW9kZWwgPC0gbW9kZWwubWF0cml4KH4gMCArIENvbmRpdGlvbiwgZGF0YT1zYW1wbGUubWV0YSkKc2ltMi5tb2RlbCA8LSBtb2RlbC5tYXRyaXgofiBDb25kaXRpb24sIGRhdGE9c2FtcGxlLm1ldGEpCmhlYWQoc2ltMi5tb2RlbCkKYGBgCgpJIGhhdmUgYSBtb2RlbCBtYXRyaXggYW5kIGNvdW50cyBtYXRyaXggLSBsZXQncyB0ZXN0IGVkZ2VSIG9uIHRoZXNlLgoKYGBge3J9CmNvdW50Lm1lYW5zIDwtIHJvd01lYW5zKHNpbTIuY291bnRzWywgcm93bmFtZXMoc2ltMi5tb2RlbCldKQpjb3VudC52YXJzIDwtIGFwcGx5KHNpbTIuY291bnRzWywgcm93bmFtZXMoc2ltMi5tb2RlbCldLCAxLCB2YXIpCgpwbG90KGNvdW50Lm1lYW5zLCBjb3VudC52YXJzKQpgYGAKClRoZSBkYXRhIGFyZSBvdmVyZGlzcGVyc2VkLiBUaGUgbW9kZWwgbm9ybWFsaXNhdGlvbiBpcyBjYXVzaW5nIHNvbWUgY29uc3Rlcm5hdGlvbi4gVGhlc2UgYWxsIHJlbHkgb24gbm9ybWFsaXNpbmcgdGhlIG5laWdoYm91cmhvb2QgY291bnRzIGJ5IHNvbWUgZmFjdG9yLiAKV2hhdCBpZiB0aGUgbm9ybWFsaXNhdGlvbiB1c2VzIHRoZSB0b3RhbCBudW1iZXIgb2YgY2VsbHMgaW4gdGhlIGV4cGVyaW1lbnQgZm9yIGVhY2ggc2FtcGxlLCByYXRoZXIgdGhhbiB0aGUgY291bnRzIGluIG5laWdoYm91cmhvb2RzLCB3aGljaCB3aWxsIGFsd2F5cyBiZSAKaGlnaGVyIGJlY2F1c2UgY2VsbHMgYXJlIGNvdW50ZWQgbXVsdGlwbGUgdGltZXMuCgpgYGB7cn0Kc2ltMi5kZ2UgPC0gREdFTGlzdChzaW0yLmNvdW50c1ssIHJvd25hbWVzKHNpbTIubW9kZWwpXSwgbGliLnNpemU9bG9nKGNvbFN1bXMoc2ltMi5jb3VudHNbLCByb3duYW1lcyhzaW0yLm1vZGVsKV0pKSkKCnNpbTIuZGdlIDwtIGVzdGltYXRlRGlzcChzaW0yLmRnZSwgc2ltMi5tb2RlbCkKc2ltMi5maXQgPC0gZ2xtUUxGaXQoc2ltMi5kZ2UsIHNpbTIubW9kZWwsIHJvYnVzdD1UUlVFKQoKc2ltMi5yZXMgPC0gYXMuZGF0YS5mcmFtZSh0b3BUYWdzKGdsbVFMRlRlc3Qoc2ltMi5maXQsIGNvZWY9MiksIHNvcnQuYnk9J25vbmUnLCBuPUluZikpCnNpbTIucmVzJE5laWdoYm91cmhvb2QgPC0gYXMubnVtZXJpYyhyb3duYW1lcyhzaW0yLnJlcykpCgpzaW0yLnNwYXRpYWxmZHIgPC0gZ3JhcGhfc3BhdGlhbEZEUihuZWlnaGJvcmhvb2RzPXZlcnRleC5saXN0LCBncmFwaD1zaW0yLmtubiwgY29ubmVjdGl2aXR5PSJkaXN0YW5jZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHB2YWx1ZXM9c2ltMi5yZXNbb3JkZXIoc2ltMi5yZXMkTmVpZ2hib3VyaG9vZCksIF0kUFZhbHVlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGNhPXNpbTIucGNhJHhbLCBjKDE6MzApXSkKCnNpbTIucmVzJFNwYXRpYWxGRFJbb3JkZXIoc2ltMi5yZXMkTmVpZ2hib3VyaG9vZCldIDwtIHNpbTIuc3BhdGlhbGZkcgpxdmFscyA8LSBzaW0yLnNwYXRpYWxmZHIKaXMuc2lnIDwtIHF2YWxzIDw9IDAuMDEKc3VtbWFyeShpcy5zaWcpCmBgYAoKVGhpcyBpcyBhdCBhIDElIEZEUi4KCmBgYHtyfQpzaW0yLm5laWdoYm91ci5leHBycyA8LSBuZWlnaGJvcmhvb2RfZXhwcmVzc2lvbih2ZXJ0ZXgubGlzdCwgc2ltMi5nZXgpCmBgYAoKRW1iZWQgdGhlc2UgaHlwZXJzcGhlcmVzIHdpdGggYSBQQ0EgYW5kIFVNQVAuCgpgYGB7cn0Kc2ltMi5uZWlnaGJvdXIucGNhIDwtIHByY29tcCgodChzaW0yLm5laWdoYm91ci5leHBycykpKQpgYGAKCgpgYGB7cn0Kc2V0LnNlZWQoNDIpCm5laWdoYm91cmhvb2QudW1hcCA8LSB1bWFwKHNpbTIubmVpZ2hib3VyLnBjYSR4WywgYygxOjMwKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fY29tcG9uZW50cz0yLAogICAgICAgICAgICAgICAgICAgICAgICAgICBuX25laWdoYm9ycz0yMSwgbWV0cmljPSdldWNsaWRlYW4nLAogICAgICAgICAgICAgICAgICAgICAgICAgICBpbml0PSdyYW5kb20nLCBtaW5fZGlzdD0wLjEpCnBsb3QobmVpZ2hib3VyaG9vZC51bWFwJGxheW91dCwKICAgICB4bGFiPSJVTUFQIDEiLCB5bGFiPSJVTUFQIDIiKQpgYGAKCldlIGNhbiBvdmVybGF5IHRoZSBEQSB0ZXN0aW5nIG9uIHRoZXNlIG5laWdoYm91cmhvb2RzLgoKYGBge3J9Cm5laWdoYm9yLmRmIDwtIHNpbTIucmVzWywgYygibG9nRkMiLCAiTmVpZ2hib3VyaG9vZCIsICJTcGF0aWFsRkRSIildCm5laWdoYm9yLmRmIDwtIGRvLmNhbGwoY2JpbmQuZGF0YS5mcmFtZSwgbGlzdChuZWlnaGJvci5kZiwgYXMuZGF0YS5mcmFtZShuZWlnaGJvdXJob29kLnVtYXAkbGF5b3V0KSkpCmNvbG5hbWVzKG5laWdoYm9yLmRmKSA8LSBjKCJsb2dGQyIsICJOZWlnaGJvdXJob29kIiwgIlNwYXRpYWxGRFIiLCAiVU1BUDEiLCAiVU1BUDIiKQpuZWlnaGJvci5kZiRTaWcgPC0gYXMubnVtZXJpYyhuZWlnaGJvci5kZiRTcGF0aWFsRkRSIDw9IDAuMDEpCgpnZ3Bsb3QobmVpZ2hib3IuZGYsIGFlcyh4PVVNQVAxLCB5PVVNQVAyKSkgKwogIGdlb21fcG9pbnQoZGF0YT1uZWlnaGJvci5kZltuZWlnaGJvci5kZiRTaWcgPT0gMCwgXSwKICAgICAgICAgICAgIGNvbG91cj0nZ3JleTgwJywgc2l6ZT0yKSArCiAgZ2VvbV9wb2ludChkYXRhPW5laWdoYm9yLmRmW25laWdoYm9yLmRmJFNpZyA9PSAxLCBdLAogICAgICAgICAgICAgYWVzKGNvbG91cj1sb2dGQyksIHNpemU9NCkgKwogIHRoZW1lX2NsZWFuKCkgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudDIobG93PSJibHVlIiwgbWlkPSJncmV5ODAiLCBoaWdoPSJyZWQiKQpgYGAKCklzIHRoYXQgYSBzdWJ0bGUgY29tcG9zaXRpb25hbCBlZmZlY3QgaW4gdGhlIDEgbmVpZ2hib3VyaG9vZCB0aGF0IGlzIGRlcGxldGVkPyBUaGF0IGNsdXN0ZXIgc2hvdWxkIG5vdCBjb250YWluIF9hbnlfIERBIG5laWdoYm91cmhvb2RzLgoKYGBge3J9Cm5laWdoYm91ci5saXN0IDwtIGxpc3QoKQpmb3IoeCBpbiBzZXFfYWxvbmcoMTpsZW5ndGgodmVydGV4Lmxpc3QpKSl7CiAgeC5kZiA8LSBtZXRhLmRmW21ldGEuZGYkVmVydGV4ICVpbiUgdmVydGV4Lmxpc3RbW3hdXSwgXQogIHgucmVwIDwtIG5hbWVzKHRhYmxlKHguZGYkUmVwbGljYXRlKSlbd2hpY2godGFibGUoeC5kZiRSZXBsaWNhdGUpID09IG1heCh0YWJsZSh4LmRmJFJlcGxpY2F0ZSkpKV0KICBpZihsZW5ndGgoeC5yZXApID4gMSl7CiAgICB4LnJlcCA8LSBzYW1wbGUoc2l6ZT0xLCB4LnJlcCkKICB9CiAgeC5ibG9jayA8LSBuYW1lcyh0YWJsZSh4LmRmJEJsb2NrKSlbd2hpY2godGFibGUoeC5kZiRCbG9jaykgPT0gbWF4KHRhYmxlKHguZGYkQmxvY2spKSldCiAgICBpZihsZW5ndGgoeC5ibG9jaykgPiAxKXsKICAgIHguYmxvY2sgPC0gc2FtcGxlKHNpemU9MSwgeC5ibG9jaykKICB9CiAgeC5jb25kaXRpb24gPC0gbmFtZXModGFibGUoeC5kZiRDb25kaXRpb24pKVt3aGljaCh0YWJsZSh4LmRmJENvbmRpdGlvbikgPT0gbWF4KHRhYmxlKHguZGYkQ29uZGl0aW9uKSkpXQogICAgaWYobGVuZ3RoKHguY29uZGl0aW9uKSA+IDEpewogICAgeC5jb25kaXRpb24gPC0gc2FtcGxlKHNpemU9MSwgeC5jb25kaXRpb24pCiAgfQogIAogIG5laWdoYm91ci5saXN0W1t4XV0gPC0gZGF0YS5mcmFtZSgiUmVwbGljYXRlIj14LnJlcCwgIkJsb2NrIj14LmJsb2NrLCAiQ29uZGl0aW9uIj14LmNvbmRpdGlvbiwgIk5laWdoYm91cmhvb2QiPXgpCn0KCm5laWdoYm91ci5tZXRhIDwtIGRvLmNhbGwocmJpbmQuZGF0YS5mcmFtZSwgbmVpZ2hib3VyLmxpc3QpCm5laWdoYm91ci5tZXJnZSA8LSBtZXJnZShuZWlnaGJvci5kZiwgbmVpZ2hib3VyLm1ldGEsIGJ5PSdOZWlnaGJvdXJob29kJykKbmVpZ2hib3VyLm1lcmdlJEJsb2NrIDwtIG9yZGVyZWQobmVpZ2hib3VyLm1lcmdlJEJsb2NrLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHM9YygiQjEiLCAiQjMiKSkKbmVpZ2hib3VyLm1lcmdlJERpZmYgPC0gc2lnbihuZWlnaGJvdXIubWVyZ2UkbG9nRkMpCm5laWdoYm91ci5tZXJnZSREaWZmW25laWdoYm91ci5tZXJnZSRTaWcgPT0gMF0gPC0gMApgYGAKCgpgYGB7ciwgZmlnLndpZHRoPTkuNzUsIGZpZy5oZWlnaHQ9NC4xNX0KZ2dwbG90KG5laWdoYm91ci5tZXJnZSwgYWVzKHg9VU1BUDEsIHk9VU1BUDIpKSArCiAgZ2VvbV9wb2ludChkYXRhPW5laWdoYm91ci5tZXJnZVssIGMoIlVNQVAxIiwgIlVNQVAyIildLAogICAgICAgICAgICAgY29sb3VyPSdncmV5ODAnLCBzaXplPTEpICsKICBnZW9tX3BvaW50KGRhdGE9bmVpZ2hib3VyLm1lcmdlW25laWdoYm91ci5tZXJnZSRTaWcgPT0gMSwgXSwKICAgICAgICAgICAgIGFlcyhjb2xvdXI9bG9nRkMpLCBzaXplPTQpICsKICB0aGVtZV9jbGVhbigpICsKICBzY2FsZV9jb2xvdXJfZ3JhZGllbnQyKGxvdz0iYmx1ZSIsIG1pZD0iZ3JleTgwIiwgaGlnaD0icmVkIikgKwogIGZhY2V0X3dyYXAofkJsb2NrKQpgYGAKClRoaXMgZG9lc24ndCBtYWtlIHNlbnNlIC0gQmxvY2sgMyBzaG91bGRuJ3QgaGF2ZSBhbnkgREEgbmVpZ2hib3VyaG9vZHMuIElzIHRoaXMgYSBjb21wb3NpdGlvbmFsIGVmZmVjdCB3ZSdyZSBzZWVpbmcgaGVyZT8gSXQncyBzdHJhbmdlIAp0aGF0IGEgcmFuZG9tIGZsdWN0dWF0aW9uIHdvdWxkIGNhdXNlIHRoaXMgLSBpdCBtdXN0IGJlIGluY3JlZGlibHkgc2Vuc2l0aXZlLiBUaGlzIGlzIGFsc28gc2FtcGxlLXNpemUgZGVwZW5kZW50LCBzbWFsbGVyIHRvdGFsIHNhbXBsZSBzaXplcyBhcmUgbGVzcyAKc3VzY2VwdGlibGUgZm9yIHNvbWUgcmVhc29uLgoKYGBge3J9CnRhYmxlKG5laWdoYm91ci5tZXJnZSRCbG9jaywgbmVpZ2hib3VyLm1lcmdlJERpZmYpCmBgYAoKVGhhdCBzaW5nbGUgZGVwbGV0ZWQgbmVpZ2hib3VyaG9vZCBpbiBCMyBpcywgSSB0aGluaywgYSBjb21wb3NpdGlvbmFsIGVmZmVjdC4gRG9lcyB0aGUgcmVmaW5lZCBzYW1wbGluZyBkZWFsIHdpdGggdGhpcyBpbiBzb21lIHdheSwgZWl0aGVyIGJ5IGhhdmluZyAKbmVpZ2hib3VyaG9vZHMgd2l0aCBsYXJnZXIsIGFuZCB0aHVzIG1vcmUgc3RhYmxlIGNvdW50cz8KCmBgYHtyLCBmaWcuaGVpZ2h0PTguOTUsIGZpZy53aWR0aD05Ljk1fQphbGwuc2FtcHMgPC0gdW5pcXVlKHBhc3RlKG1ldGEuZGYkQmxvY2ssIG1ldGEuZGYkQ29uZGl0aW9uLCBtZXRhLmRmJFJlcGxpY2F0ZSwgc2VwPSJfIikpCm1ldGEuZGYkQWxsLlNhbXBsZSA8LSBwYXN0ZShtZXRhLmRmJEJsb2NrLCBtZXRhLmRmJENvbmRpdGlvbiwgbWV0YS5kZiRSZXBsaWNhdGUsIHNlcD0iXyIpCmFsbC5jb3VudC5tYXRyaXggPC0gbWF0cml4KDBMLCBuY29sPWxlbmd0aChhbGwuc2FtcHMpLCBucm93PWxlbmd0aCh2ZXJ0ZXgubGlzdCkpCmNvbG5hbWVzKGFsbC5jb3VudC5tYXRyaXgpIDwtIGFsbC5zYW1wcwogIApmb3IoeCBpbiBzZXFfYWxvbmcoMTpsZW5ndGgodmVydGV4Lmxpc3QpKSl7CiAgdi54IDwtIHZlcnRleC5saXN0W1t4XV0KICBmb3IoaSBpbiBzZXFfYWxvbmcoMTpsZW5ndGgoYWxsLnNhbXBzKSkpewogICAgaS5zIDwtIGFsbC5zYW1wc1tpXQogICAgaS5zLnZlcnRpY2VzIDwtIGludGVyc2VjdCh2LngsIG1ldGEuZGZbbWV0YS5kZiRBbGwuU2FtcGxlID09IGkucywgXSRWZXJ0ZXgpCiAgICBhbGwuY291bnQubWF0cml4W3gsIGldIDwtIGxlbmd0aChpLnMudmVydGljZXMpCiAgfQp9CgphbGwuY291bnQubWVsdCA8LSBtZWx0KGFsbC5jb3VudC5tYXRyaXgpCmFsbC5jb3VudC5tZWx0JFZhcjIgPC0gYXMuY2hhcmFjdGVyKGFsbC5jb3VudC5tZWx0JFZhcjIpCmFsbC5jb3VudC5tZWx0JEJsb2NrIDwtIHVubGlzdChsYXBwbHkoc3Ryc3BsaXQoYWxsLmNvdW50Lm1lbHQkVmFyMiwgc3BsaXQ9Il8iLCBmaXhlZD1UUlVFKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGVU49ZnVuY3Rpb24oWFApIHBhc3RlMChYUFsxXSkpKQphbGwuY291bnQubWVsdCRDb25kaXRpb24gPC0gdW5saXN0KGxhcHBseShzdHJzcGxpdChhbGwuY291bnQubWVsdCRWYXIyLCBzcGxpdD0iXyIsIGZpeGVkPVRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZVTj1mdW5jdGlvbihYUCkgcGFzdGUwKFhQWzJdKSkpCmFsbC5jb3VudC5tZWx0JFJlcGxpY2F0ZSA8LSB1bmxpc3QobGFwcGx5KHN0cnNwbGl0KGFsbC5jb3VudC5tZWx0JFZhcjIsIHNwbGl0PSJfIiwgZml4ZWQ9VFJVRSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOPWZ1bmN0aW9uKFhQKSBwYXN0ZTAoWFBbM10pKSkKCmdncGxvdChhbGwuY291bnQubWVsdFthbGwuY291bnQubWVsdCRWYXIxICVpbiUgYyhuZWlnaGJvdXIubWVyZ2UkTmVpZ2hib3VyaG9vZFtuZWlnaGJvdXIubWVyZ2UkU2lnID09IDFdKSAmCiAgICAgICAgICAgICAgICAgICAgICAgIGFsbC5jb3VudC5tZWx0JEJsb2NrICVpbiUgYygiQjMiKSwgXSwKICAgICAgIGFlcyh4PUJsb2NrLCB5PXZhbHVlLCBmaWxsPUNvbmRpdGlvbikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgdGhlbWVfY2xlYW4oKSArCiAgZmFjZXRfd3JhcCh+VmFyMSwgc2NhbGVzPSJmcmVlX3kiKQpgYGAKClRoZXNlIGFyZSB0aGUgY291bnRzIGZvciBCMyBpbiB0aGUgREFOcy4gTjIxIGxvb2tzIGxpa2UgdGhlIGVmZmVjdCBpcyBmcm9tIHNhbXBsaW5nIHZhcmlhbmNlLCBhcyB0aGUgY291bnRzIGFyZSByZWFsbHkgcXVpdGUgbG93LgoKIyMjIFRlc3QgdXNpbmcgcmVmaW5lZCBzYW1wbGluZwoKYGBge3J9CnJlZmluZV92ZXJ0ZXggPC0gZnVuY3Rpb24odmVydGV4Lmtubiwgdi5peCwgWF9wY2EpewogICMgdmVydGV4LmtubjogS05OIGdyYXBoIGZvciByYW5kb21seSBzYW1wbGVkIHBvaW50cyAob3V0cHV0IG9mIEJpb2NOZWlnaGJvcnM6OmZpbmRLTk4pCiAgIyB2Lml4OiBpbmRleCBvZiB2ZXJ0ZXggdG8gcmVmaW5lIGluIHZlcnRleC5rbm4KICAKICAjIyBDYWxjdWxhdGUgbWVkaWFuIHByb2ZpbGUgb2YgS05OcyBvZiB2ZXJ0ZXgKICB2Lm1lZCA8LSBhcHBseShYX3BjYVt2ZXJ0ZXgua25uJGluZGV4W3YuaXgsXSxdLCAyLCBtZWRpYW4pCiAgIyMgRmluZCB0aGUgY2xvc2VzdCBwb2ludCB0byB0aGUgbWVkaWFuIGFuZCBzYW1wbGUKICByZWZpbmVkLnZlcnRleCA8LSBCaW9jTmVpZ2hib3JzOjpmaW5kS05OKHJiaW5kKHYubWVkLCBYX3BjYSksIHN1YnNldD0xLCBrPTEpW1siaW5kZXgiXV1bMV0gLSAxICMjIC0xIHRvIHJlbW92ZSB0aGUgbWVkaWFuCiAgcmV0dXJuKHJlZmluZWQudmVydGV4KQp9CgoKcXVhbnRfbmVpZ2hib3VyaG9vZCA8LSBmdW5jdGlvbihncmFwaCwgbWV0YSwgc2FtcGxlLmNvbHVtbj0nU2FtcGxlJywgc2FtcGxlLnZlcnRpY2VzPTAuMjUsIHNlZWQ9NDIsIHBjYT1OVUxMLCBzYW1wbGU9InJhbmRvbSIpewogIHNldC5zZWVkKHNlZWQpCiAgCiAgaWYoc2FtcGxlID09ICJyYW5kb20iKXsKICAjIGRlZmluZSBhIHNldCBvZiB2ZXJ0aWNlcyBhbmQgbmVpaGJvdXJob29kIGNlbnRlcnMgLSBleHRyYWN0IHRoZSBuZWloYm91cmhvb2RzIG9mIHRoZXNlIGNlbGxzCiAgcmFuZG9tLnZlcnRpY2VzIDwtIHNhbXBsZShWKGdyYXBoKSwgc2l6ZT1mbG9vcihzYW1wbGUudmVydGljZXMqbGVuZ3RoKFYoZ3JhcGgpKSkpCiAgdmVydGV4Lmxpc3QgPC0gc2FwcGx5KDE6bGVuZ3RoKHJhbmRvbS52ZXJ0aWNlcyksIEZVTj1mdW5jdGlvbihYKSBuZWlnaGJvcnMoZ3JhcGgsIHY9cmFuZG9tLnZlcnRpY2VzW1hdKSkKICB9IGVsc2UgaWYoc2FtcGxlID09ICJyZWZpbmVkIil7CiAgICBpZihpcy5udWxsKHBjYSkpewogICAgICBzdG9wKCJQbGVhc2UgcGFzcyBhIFBDQSBvYmplY3QgLSBleHBlY3RlZCBvdXRwdXQgZnJvbSBwcmNvbXAoKSIpCiAgICB9CiAgICBYX3BjYSA8LSBwY2EkeFssIGMoMTozMCldCiAgICAKICAgIHJhbmRvbS52ZXJ0aWNlcyA8LSBzYW1wbGUoVihncmFwaCksIHNpemU9Zmxvb3Ioc2FtcGxlLnZlcnRpY2VzKmxlbmd0aChWKGdyYXBoKSkpKQogICAgdmVydGV4LmtubiA8LSBCaW9jTmVpZ2hib3JzOjpmaW5kS05OKFg9WF9wY2EsIGs9MjEsIHN1YnNldD1hcy52ZWN0b3IocmFuZG9tLnZlcnRpY2VzKSkKICAgIHJlZmluZWQudmVydGljZXMgPC0gVihncmFwaClbc2FwcGx5KDE6bnJvdyh2ZXJ0ZXgua25uJGluZGV4KSwgZnVuY3Rpb24oaSkgcmVmaW5lX3ZlcnRleCh2ZXJ0ZXgua25uLCBpLCBYX3BjYSkpXQogIAogICAgdmVydGV4Lmxpc3QgPC0gc2FwcGx5KDE6bGVuZ3RoKHJhbmRvbS52ZXJ0aWNlcyksIEZVTj1mdW5jdGlvbihYKSBuZWlnaGJvcnMoZ3JhcGgsIHY9cmFuZG9tLnZlcnRpY2VzW1hdKSkKICAgIHZlcnRleC5saXN0LnJlZmluZWQgPC0gc2FwcGx5KDE6bGVuZ3RoKHJlZmluZWQudmVydGljZXMpLCBGVU49ZnVuY3Rpb24oWCkgbmVpZ2hib3JzKGdyYXBoLCB2PXJlZmluZWQudmVydGljZXNbWF0pKSAgCiAgICB2ZXJ0ZXgubGlzdCA8LSB2ZXJ0ZXgubGlzdC5yZWZpbmVkCiAgfQogIAogIGNvdW50Lm1hdHJpeCA8LSBtYXRyaXgoMEwsIG5jb2w9bGVuZ3RoKHVuaXF1ZShtZXRhWywgc2FtcGxlLmNvbHVtbl0pKSwgbnJvdz1sZW5ndGgodmVydGV4Lmxpc3QpKQogIGNvbG5hbWVzKGNvdW50Lm1hdHJpeCkgPC0gdW5pcXVlKG1ldGFbLCBzYW1wbGUuY29sdW1uXSkKICAKICBmb3IoeCBpbiBzZXFfYWxvbmcoMTpsZW5ndGgodmVydGV4Lmxpc3QpKSl7CiAgICB2LnggPC0gdmVydGV4Lmxpc3RbW3hdXQogICAgZm9yKGkgaW4gc2VxX2Fsb25nKDE6bGVuZ3RoKHVuaXF1ZShtZXRhWywgc2FtcGxlLmNvbHVtbl0pKSkpewogICAgICBpLnMgPC0gdW5pcXVlKG1ldGFbLCBzYW1wbGUuY29sdW1uXSlbaV0KICAgICAgaS5zLnZlcnRpY2VzIDwtIGludGVyc2VjdCh2LngsIG1ldGFbbWV0YVssIHNhbXBsZS5jb2x1bW5dID09IGkucywgXSRWZXJ0ZXgpCiAgICAgIGNvdW50Lm1hdHJpeFt4LCBpXSA8LSBsZW5ndGgoaS5zLnZlcnRpY2VzKQogICAgfQogIH0KICByb3duYW1lcyhjb3VudC5tYXRyaXgpIDwtIGMoMTpsZW5ndGgodmVydGV4Lmxpc3QpKQogIHJldHVybihjb3VudC5tYXRyaXgpCn0KYGBgCgoKYGBge3J9CnNpbTIuY291bnRzIDwtIHF1YW50X25laWdoYm91cmhvb2QoZ3JhcGg9c2ltMi5rbm4sIG1ldGE9bWV0YS5kZiwgc2FtcGxlLmNvbHVtbj0nU2FtcGxlJywgc2FtcGxlLnZlcnRpY2VzPW4uaG9vZCwgc2FtcGxlPSJyZWZpbmVkIiwgcGNhPXNpbTIucGNhKQpzYW1wbGUubWV0YSA8LSBkYXRhLmZyYW1lKCJDb25kaXRpb24iPWMocmVwKCJBIiwgMyksIHJlcCgiQiIsIDMpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAiUmVwbGljYXRlIj1yZXAoYygiUjEiLCAiUjIiLCAiUjMiKSwgMikpCnNhbXBsZS5tZXRhJFNhbXBsZSA8LSBwYXN0ZShzYW1wbGUubWV0YSRDb25kaXRpb24sIHNhbXBsZS5tZXRhJFJlcGxpY2F0ZSwgc2VwPSJfIikKcm93bmFtZXMoc2FtcGxlLm1ldGEpIDwtIHNhbXBsZS5tZXRhJFNhbXBsZQojIHNpbTIubW9kZWwgPC0gbW9kZWwubWF0cml4KH4gMCArIENvbmRpdGlvbiwgZGF0YT1zYW1wbGUubWV0YSkKc2ltMi5tb2RlbCA8LSBtb2RlbC5tYXRyaXgofiBDb25kaXRpb24sIGRhdGE9c2FtcGxlLm1ldGEpCmhlYWQoc2ltMi5tb2RlbCkKYGBgCgpJIGhhdmUgYSBtb2RlbCBtYXRyaXggYW5kIGNvdW50cyBtYXRyaXggLSBsZXQncyB0ZXN0IGVkZ2VSIG9uIHRoZXNlLgoKYGBge3J9CnNpbTIuZGdlIDwtIERHRUxpc3Qoc2ltMi5jb3VudHNbLCByb3duYW1lcyhzaW0yLm1vZGVsKV0sIGxpYi5zaXplPWxvZyhjb2xTdW1zKHNpbTIuY291bnRzWywgcm93bmFtZXMoc2ltMi5tb2RlbCldKSkpCnNpbTIuZGdlIDwtIGVzdGltYXRlRGlzcChzaW0yLmRnZSwgc2ltMi5tb2RlbCwgdGFnd2lzZT1UUlVFKQpzaW0yLmZpdCA8LSBnbG1RTEZpdChzaW0yLmRnZSwgc2ltMi5tb2RlbCwgcm9idXN0PVRSVUUpCiMgc2ltMi5jb250cmFzdCA8LSBtYWtlQ29udHJhc3RzKENvbmRpdGlvbkEgLSBDb25kaXRpb25CLCBsZXZlbHM9c2ltMi5tb2RlbCkKIyBzaW0yLnJlcyA8LSBnbG1RTEZUZXN0KHNpbTIuZml0LCBjb250cmFzdD1zaW0yLmNvbnRyYXN0KQoKc2ltMi5yZXMgPC0gYXMuZGF0YS5mcmFtZSh0b3BUYWdzKGdsbVFMRlRlc3Qoc2ltMi5maXQsIGNvZWY9MiksIHNvcnQuYnk9J25vbmUnLCBuPUluZikpCnNpbTIucmVzJE5laWdoYm91cmhvb2QgPC0gYXMubnVtZXJpYyhyb3duYW1lcyhzaW0yLnJlcykpCgpzaW0yLnNwYXRpYWxmZHIgPC0gZ3JhcGhfc3BhdGlhbEZEUihuZWlnaGJvcmhvb2RzPXZlcnRleC5saXN0LnJlZmluZWQsIGdyYXBoPXNpbTIua25uLCBjb25uZWN0aXZpdHk9ImRpc3RhbmNlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHZhbHVlcz1zaW0yLnJlc1tvcmRlcihzaW0yLnJlcyROZWlnaGJvdXJob29kKSwgXSRQVmFsdWUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwY2E9c2ltMi5wY2EkeFssIGMoMTozMCldKQoKc2ltMi5yZXMkU3BhdGlhbEZEUltvcmRlcihzaW0yLnJlcyROZWlnaGJvdXJob29kKV0gPC0gc2ltMi5zcGF0aWFsZmRyCnF2YWxzIDwtIHNpbTIuc3BhdGlhbGZkcgppcy5zaWcgPC0gcXZhbHMgPD0gMC4wMQpzdW1tYXJ5KGlzLnNpZykKYGBgCgpUaGF0J3MgYSBsb3Qgb2YgREEgbmVpZ2Jib3VyaG9vZHMhCgpgYGB7cn0Kc2ltMi5uZWlnaGJvdXIuZXhwcnMgPC0gbmVpZ2hib3Job29kX2V4cHJlc3Npb24odmVydGV4Lmxpc3QucmVmaW5lZCwgc2ltMi5nZXgpCmBgYAoKRW1iZWQgdGhlc2UgaHlwZXJzcGhlcmVzIHdpdGggYSBQQ0EgYW5kIFVNQVAuCgpgYGB7cn0Kc2ltMi5uZWlnaGJvdXIucGNhIDwtIHByY29tcCgodChzaW0yLm5laWdoYm91ci5leHBycykpKQpgYGAKCgpgYGB7cn0Kc2V0LnNlZWQoNDIpCm5laWdoYm91cmhvb2QudW1hcCA8LSB1bWFwKHNpbTIubmVpZ2hib3VyLnBjYSR4WywgYygxOjMwKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fY29tcG9uZW50cz0yLAogICAgICAgICAgICAgICAgICAgICAgICAgICBuX25laWdoYm9ycz0yMSwgbWV0cmljPSdldWNsaWRlYW4nLAogICAgICAgICAgICAgICAgICAgICAgICAgICBpbml0PSdyYW5kb20nLCBtaW5fZGlzdD0wLjEpCnBsb3QobmVpZ2hib3VyaG9vZC51bWFwJGxheW91dCwKICAgICB4bGFiPSJVTUFQIDEiLCB5bGFiPSJVTUFQIDIiKQpgYGAKCldlIGNhbiBvdmVybGF5IHRoZSBEQSB0ZXN0aW5nIG9uIHRoZXNlIG5laWdoYm91cmhvb2RzLgoKYGBge3J9Cm5laWdoYm9yLmRmIDwtIHNpbTIucmVzWywgYygibG9nRkMiLCAiTmVpZ2hib3VyaG9vZCIsICJTcGF0aWFsRkRSIildCm5laWdoYm9yLmRmIDwtIGRvLmNhbGwoY2JpbmQuZGF0YS5mcmFtZSwgbGlzdChuZWlnaGJvci5kZiwgYXMuZGF0YS5mcmFtZShuZWlnaGJvdXJob29kLnVtYXAkbGF5b3V0KSkpCmNvbG5hbWVzKG5laWdoYm9yLmRmKSA8LSBjKCJsb2dGQyIsICJOZWlnaGJvdXJob29kIiwgIlNwYXRpYWxGRFIiLCAiVU1BUDEiLCAiVU1BUDIiKQpuZWlnaGJvci5kZiRTaWcgPC0gYXMubnVtZXJpYyhuZWlnaGJvci5kZiRTcGF0aWFsRkRSIDw9IDAuMDEpCgpnZ3Bsb3QobmVpZ2hib3IuZGYsIGFlcyh4PVVNQVAxLCB5PVVNQVAyKSkgKwogIGdlb21fcG9pbnQoZGF0YT1uZWlnaGJvci5kZltuZWlnaGJvci5kZiRTaWcgPT0gMCwgXSwKICAgICAgICAgICAgIGNvbG91cj0nZ3JleTgwJywgc2l6ZT0yKSArCiAgZ2VvbV9wb2ludChkYXRhPW5laWdoYm9yLmRmW25laWdoYm9yLmRmJFNpZyA9PSAxLCBdLAogICAgICAgICAgICAgYWVzKGNvbG91cj1sb2dGQyksIHNpemU9NCkgKwogIHRoZW1lX2NsZWFuKCkgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudDIobG93PSJibHVlIiwgbWlkPSJncmV5ODAiLCBoaWdoPSJyZWQiKQpgYGAKCk5vIG1vcmUgZmFsc2UgREFOcyBpbiBCMywgZGVzcGl0ZSB0aGUgc2FtZSBudW1iZXIgb2YgbmVpZ2hib3VyaG9vZHMgYmVpbmcgY291bnRlZC4KCmBgYHtyfQpuZWlnaGJvdXIubGlzdCA8LSBsaXN0KCkKZm9yKHggaW4gc2VxX2Fsb25nKDE6bGVuZ3RoKHZlcnRleC5saXN0LnJlZmluZWQpKSl7CiAgeC5kZiA8LSBtZXRhLmRmW21ldGEuZGYkVmVydGV4ICVpbiUgdmVydGV4Lmxpc3QucmVmaW5lZFtbeF1dLCBdCiAgeC5yZXAgPC0gbmFtZXModGFibGUoeC5kZiRSZXBsaWNhdGUpKVt3aGljaCh0YWJsZSh4LmRmJFJlcGxpY2F0ZSkgPT0gbWF4KHRhYmxlKHguZGYkUmVwbGljYXRlKSkpXQogIGlmKGxlbmd0aCh4LnJlcCkgPiAxKXsKICAgIHgucmVwIDwtIHNhbXBsZShzaXplPTEsIHgucmVwKQogIH0KICB4LmJsb2NrIDwtIG5hbWVzKHRhYmxlKHguZGYkQmxvY2spKVt3aGljaCh0YWJsZSh4LmRmJEJsb2NrKSA9PSBtYXgodGFibGUoeC5kZiRCbG9jaykpKV0KICAgIGlmKGxlbmd0aCh4LmJsb2NrKSA+IDEpewogICAgeC5ibG9jayA8LSBzYW1wbGUoc2l6ZT0xLCB4LmJsb2NrKQogIH0KICB4LmNvbmRpdGlvbiA8LSBuYW1lcyh0YWJsZSh4LmRmJENvbmRpdGlvbikpW3doaWNoKHRhYmxlKHguZGYkQ29uZGl0aW9uKSA9PSBtYXgodGFibGUoeC5kZiRDb25kaXRpb24pKSldCiAgICBpZihsZW5ndGgoeC5jb25kaXRpb24pID4gMSl7CiAgICB4LmNvbmRpdGlvbiA8LSBzYW1wbGUoc2l6ZT0xLCB4LmNvbmRpdGlvbikKICB9CiAgCiAgbmVpZ2hib3VyLmxpc3RbW3hdXSA8LSBkYXRhLmZyYW1lKCJSZXBsaWNhdGUiPXgucmVwLCAiQmxvY2siPXguYmxvY2ssICJDb25kaXRpb24iPXguY29uZGl0aW9uLCAiTmVpZ2hib3VyaG9vZCI9eCkKfQoKbmVpZ2hib3VyLm1ldGEgPC0gZG8uY2FsbChyYmluZC5kYXRhLmZyYW1lLCBuZWlnaGJvdXIubGlzdCkKbmVpZ2hib3VyLm1lcmdlIDwtIG1lcmdlKG5laWdoYm9yLmRmLCBuZWlnaGJvdXIubWV0YSwgYnk9J05laWdoYm91cmhvb2QnKQpuZWlnaGJvdXIubWVyZ2UkQmxvY2sgPC0gb3JkZXJlZChuZWlnaGJvdXIubWVyZ2UkQmxvY2ssCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscz1jKCJCMSIsICJCMyIpKQpuZWlnaGJvdXIubWVyZ2UkRGlmZiA8LSBzaWduKG5laWdoYm91ci5tZXJnZSRsb2dGQykKbmVpZ2hib3VyLm1lcmdlJERpZmZbbmVpZ2hib3VyLm1lcmdlJFNpZyA9PSAwXSA8LSAwCmBgYAoKCmBgYHtyLCBmaWcud2lkdGg9OS43NSwgZmlnLmhlaWdodD00LjE1fQpnZ3Bsb3QobmVpZ2hib3VyLm1lcmdlLCBhZXMoeD1VTUFQMSwgeT1VTUFQMikpICsKICBnZW9tX3BvaW50KGRhdGE9bmVpZ2hib3VyLm1lcmdlWywgYygiVU1BUDEiLCAiVU1BUDIiKV0sCiAgICAgICAgICAgICBjb2xvdXI9J2dyZXk4MCcsIHNpemU9MSkgKwogIGdlb21fcG9pbnQoZGF0YT1uZWlnaGJvdXIubWVyZ2VbbmVpZ2hib3VyLm1lcmdlJFNpZyA9PSAxLCBdLAogICAgICAgICAgICAgYWVzKGNvbG91cj1sb2dGQyksIHNpemU9NCkgKwogIHRoZW1lX2NsZWFuKCkgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudDIobG93PSJibHVlIiwgbWlkPSJncmV5ODAiLCBoaWdoPSJyZWQiKSArCiAgZmFjZXRfd3JhcCh+QmxvY2spCmBgYAoKVGhpcyBkb2Vzbid0IG1ha2Ugc2Vuc2UgLSBCbG9jayAzIHNob3VsZG4ndCBoYXZlIGFueSBEQSBuZWlnaGJvdXJob29kcy4gSXMgdGhpcyBhIGNvbXBvc2l0aW9uYWwgZWZmZWN0IHdlJ3JlIHNlZWluZyBoZXJlPyBJdCdzIHN0cmFuZ2UgCnRoYXQgYSByYW5kb20gZmx1Y3R1YXRpb24gd291bGQgY2F1c2UgdGhpcyAtIGl0IG11c3QgYmUgaW5jcmVkaWJseSBzZW5zaXRpdmUuCgpgYGB7cn0KdGFibGUobmVpZ2hib3VyLm1lcmdlJEJsb2NrLCBuZWlnaGJvdXIubWVyZ2UkRGlmZikKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04Ljk1LCBmaWcud2lkdGg9OS45NX0KYWxsLnNhbXBzIDwtIHVuaXF1ZShwYXN0ZShtZXRhLmRmJEJsb2NrLCBtZXRhLmRmJENvbmRpdGlvbiwgbWV0YS5kZiRSZXBsaWNhdGUsIHNlcD0iXyIpKQptZXRhLmRmJEFsbC5TYW1wbGUgPC0gcGFzdGUobWV0YS5kZiRCbG9jaywgbWV0YS5kZiRDb25kaXRpb24sIG1ldGEuZGYkUmVwbGljYXRlLCBzZXA9Il8iKQphbGwuY291bnQubWF0cml4IDwtIG1hdHJpeCgwTCwgbmNvbD1sZW5ndGgoYWxsLnNhbXBzKSwgbnJvdz1sZW5ndGgodmVydGV4Lmxpc3QucmVmaW5lZCkpCmNvbG5hbWVzKGFsbC5jb3VudC5tYXRyaXgpIDwtIGFsbC5zYW1wcwogIApmb3IoeCBpbiBzZXFfYWxvbmcoMTpsZW5ndGgodmVydGV4Lmxpc3QucmVmaW5lZCkpKXsKICB2LnggPC0gdmVydGV4Lmxpc3QucmVmaW5lZFtbeF1dCiAgZm9yKGkgaW4gc2VxX2Fsb25nKDE6bGVuZ3RoKGFsbC5zYW1wcykpKXsKICAgIGkucyA8LSBhbGwuc2FtcHNbaV0KICAgIGkucy52ZXJ0aWNlcyA8LSBpbnRlcnNlY3Qodi54LCBtZXRhLmRmW21ldGEuZGYkQWxsLlNhbXBsZSA9PSBpLnMsIF0kVmVydGV4KQogICAgYWxsLmNvdW50Lm1hdHJpeFt4LCBpXSA8LSBsZW5ndGgoaS5zLnZlcnRpY2VzKQogIH0KfQoKYWxsLmNvdW50Lm1lbHQgPC0gbWVsdChhbGwuY291bnQubWF0cml4KQphbGwuY291bnQubWVsdCRWYXIyIDwtIGFzLmNoYXJhY3RlcihhbGwuY291bnQubWVsdCRWYXIyKQphbGwuY291bnQubWVsdCRCbG9jayA8LSB1bmxpc3QobGFwcGx5KHN0cnNwbGl0KGFsbC5jb3VudC5tZWx0JFZhcjIsIHNwbGl0PSJfIiwgZml4ZWQ9VFJVRSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRlVOPWZ1bmN0aW9uKFhQKSBwYXN0ZTAoWFBbMV0pKSkKYWxsLmNvdW50Lm1lbHQkQ29uZGl0aW9uIDwtIHVubGlzdChsYXBwbHkoc3Ryc3BsaXQoYWxsLmNvdW50Lm1lbHQkVmFyMiwgc3BsaXQ9Il8iLCBmaXhlZD1UUlVFKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGVU49ZnVuY3Rpb24oWFApIHBhc3RlMChYUFsyXSkpKQphbGwuY291bnQubWVsdCRSZXBsaWNhdGUgPC0gdW5saXN0KGxhcHBseShzdHJzcGxpdChhbGwuY291bnQubWVsdCRWYXIyLCBzcGxpdD0iXyIsIGZpeGVkPVRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZVTj1mdW5jdGlvbihYUCkgcGFzdGUwKFhQWzNdKSkpCgpnZ3Bsb3QoYWxsLmNvdW50Lm1lbHRbYWxsLmNvdW50Lm1lbHQkVmFyMSAlaW4lIGMobmVpZ2hib3VyLm1lcmdlJE5laWdoYm91cmhvb2RbbmVpZ2hib3VyLm1lcmdlJFNpZyA9PSAxXSkgJgogICAgICAgICAgICAgICAgICAgICAgICBhbGwuY291bnQubWVsdCRCbG9jayAlaW4lIGMoIkIzIiksIF0sCiAgICAgICBhZXMoeD1CbG9jaywgeT12YWx1ZSwgZmlsbD1Db25kaXRpb24pKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHRoZW1lX2NsZWFuKCkgKwogIGZhY2V0X3dyYXAoflZhcjEsIHNjYWxlcz0iZnJlZV95IikKYGBgCgoKCiMgTmV3IGJ1aWxkS05OR3JhcGgoKSB0byByZXRhaW4gY2VsbC1jZWxsIGRpc3RhbmNlcwoKSWYgd2Ugd2FudCB0byBiYXNlIHRoZSBzcGF0aWFsIEZEUiBjb3JyZWN0aW9uIG9uIGEgZGlzdGFuY2UgdGhlbiB3ZSBuZWVkIHNvbWUgd2F5IHRvIHJldGFpbiB0aGlzIGluZm9ybWF0aW9uIGluIHRoZSBncmFwaCB0byBzcGVlZC11cCB0aGUgY2FsY3VsYXRpb25zIGZvciAKbGFyZ2UgYW5kIGhpZ2hseS1jb25uZWN0ZWQgZ3JhcGhzLiBPdXIgaWRlYSBpcyB0byByZS1jb2RlIHRoZSBgYnVpbGRLTk5HcmFwaCgpYCBmdW5jdGlvbiB0byByZXRhaW4gY2VsbC1jZWxsIGRpc3RhbmNlcyBpbiB0aGUgZWRnZSB3ZWlnaHRzIHNsb3Qgb2YgdGhlIAppZ3JhcGggb2JqZWN0LgoKYGBge3IsIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMgcmVxdWlyZShCaW9jTmVpZ2hib3JzKQojIAojIG5laWdoYm9yc1RvS05OR3JhcGggPC0gZnVuY3Rpb24obm4sIGRpcmVjdGVkPUZBTFNFLCBkaXN0YW5jZXM9RkFMU0UpIHsKIyAgIGluZGljZXMgPC0gbm4kaW5kZXgKIyAgIHN0YXJ0IDwtIGFzLnZlY3Rvcihyb3coaW5kaWNlcykpCiMgICBlbmQgPC0gYXMudmVjdG9yKGluZGljZXMpCiMgICBpbnRlcmxlYXZlZCA8LSBhcy52ZWN0b3IocmJpbmQoc3RhcnQsIGVuZCkpCiMgICAKIyAgIAojICAgaWYgKGRpcmVjdGVkKSB7CiMgICAgIGcgPC0gaWdyYXBoOjptYWtlX2dyYXBoKGludGVybGVhdmVkLCBkaXJlY3RlZD1UUlVFKQojICAgICAKIyAgICAgaWYoaXNGQUxTRShkaXN0YW5jZXMpKXsKIyAgICAgICBFKGcpJHdlaWdodCA8LSBOQQojICAgICB9IGVsc2V7CiMgICAgICAgZGlzdHMgPC0gYXMuZGF0YS5mcmFtZShtZWx0KG5uJGRpc3RhbmNlKSkKIyAgICAgICBjb2xuYW1lcyhkaXN0cykgPC0gYygiZnJvbSIsICJ0byIsICJ3ZWlnaHQiKQojICAgICAgICMgcmVtb3ZlIGVkZ2VzIHRoYXQgYXJlIG11bHRpcGxlcwojICAgICAgIGQuZGYgPC0gZ3JhcGguZGF0YS5mcmFtZShkaXN0cykKIyAgICAgICBFKGcpJHdlaWdodCA8LSBkLmRmCiMgICAgIAojICAgICB9CiMgICB9IGVsc2UgewojICAgICBnIDwtIGlncmFwaDo6bWFrZV9ncmFwaChpbnRlcmxlYXZlZCwgZGlyZWN0ZWQ9RkFMU0UpCiMgICAgIAojICAgICBpZihpc0ZBTFNFKGRpc3RhbmNlcykpewojICAgICAgIEUoZykkd2VpZ2h0IDwtIE5BCiMgICAgIH0gZWxzZXsKIyAgICAgICBkaXN0cyA8LSBhcy5kYXRhLmZyYW1lKG1lbHQobm4kZGlzdGFuY2UpKQojICAgICAgIGNvbG5hbWVzKGRpc3RzKSA8LSBjKCJmcm9tIiwgInRvIiwgIndlaWdodCIpCiMgICAgICAgZC5kZiA8LSBncmFwaC5kYXRhLmZyYW1lKGRpc3RzKQojICAgICAgIEUoZykkd2VpZ2h0IDwtIGQuZGYKIyAgICAgfQojICAgICAgIAojICAgICBnIDwtIGlncmFwaDo6c2ltcGxpZnkoZywgZWRnZS5hdHRyLmNvbWIgPSAiZmlyc3QiKQojICAgICB9CiMgICBnCiMgfQojIAojIAojIC5idWlsZEtOTkdyYXBoIDwtIGZ1bmN0aW9uKHgsIGs9MTAsIGQ9NTAsIGRpcmVjdGVkPUZBTFNFLCB0cmFuc3Bvc2VkPUZBTFNFLCBrZWVwLmRpc3RhbmNlPUZBTFNFLAojICAgICBzdWJzZXQucm93PU5VTEwsIEJOUEFSQU09S21rbm5QYXJhbSgpLCBCU1BBUkFNPWJzcGFyYW0oKSwgQlBQQVJBTT1TZXJpYWxQYXJhbSgpKQojICAgewojICAgIyB0aGlzIHdpbGwgbm93IHJldGFpbiB0aGUgZGlzdGFuY2VzIHRvIG5laWdoYm91cnMgLSBuZWVkIHRvIHBhc3MgdGhpcyB0byBgbmVpZ2hib3JzVG9LTk5HcmFwaGAKIyAgIG5uLm91dCA8LSAuc2V0dXBfa25uX2RhdGEoeD14LCBzdWJzZXQucm93PXN1YnNldC5yb3csIGQ9ZCwgdHJhbnNwb3NlZD10cmFuc3Bvc2VkLCBrZWVwLmRpc3RhbmNlPWtlZXAuZGlzdGFuY2UsCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGs9aywgQk5QQVJBTT1CTlBBUkFNLCBCU1BBUkFNPUJTUEFSQU0sIEJQUEFSQU09QlBQQVJBTSkKIyAgIHNpbmsoZmlsZT0iL2Rldi9udWxsIikKIyAgIGdjKCkKIyAgIHNpbmsoZmlsZT1OVUxMKQojICAgIyB0ZXN0IGZvciBwcmVzZW5jZSBvZiBkaXN0YW5jZXMKIyAgIG5laWdoYm9yc1RvS05OR3JhcGgobm4ub3V0LCBkaXJlY3RlZD1kaXJlY3RlZCwgZGlzdGFuY2VzPWtlZXAuZGlzdGFuY2UpCiMgfQojIAojIAojICMnIEBpbXBvcnRGcm9tIEJpb2NOZWlnaGJvcnMgZmluZEtOTgojIC5zZXR1cF9rbm5fZGF0YSA8LSBmdW5jdGlvbih4LCBzdWJzZXQucm93LCBkLCB0cmFuc3Bvc2VkLCBrLCBCTlBBUkFNLCBCU1BBUkFNLCBCUFBBUkFNLCBrZWVwLmRpc3RhbmNlPWtlZXAuZGlzdGFuY2UpIHsKIyAgICAgaWYgKCF0cmFuc3Bvc2VkKSB7CiMgICAgICAgICBpZiAoIWlzLm51bGwoc3Vic2V0LnJvdykpIHsKIyAgICAgICAgICAgICB4IDwtIHhbc3Vic2V0LnJvdywsZHJvcD1GQUxTRV0KIyAgICAgICAgIH0KIyAgICAgICAgIHggPC0gdCh4KQojICAgICB9IAojICAgICAKIyAgICAgIyBSZWR1Y2luZyBkaW1lbnNpb25zLCBpZiAnZCcgaXMgbGVzcyB0aGFuIHRoZSBudW1iZXIgb2YgZ2VuZXMuCiMgICAgIGlmICghaXMubmEoZCkgJiYgZCA8IG5jb2woeCkpIHsKIyAgICAgICAgIHN2ZC5vdXQgPC0gLmNlbnRlcmVkX1NWRCh4LCBtYXgucmFuaz1kLCBrZWVwLnJpZ2h0PUZBTFNFLCBCU1BBUkFNPUJTUEFSQU0sIEJQUEFSQU09QlBQQVJBTSkKIyAgICAgICAgIHggPC0gLnN2ZF90b19wY2Eoc3ZkLm91dCwgZCwgbmFtZWQ9RkFMU0UpCiMgICAgIH0KIyAgICAKIyAgICAgIyBGaW5kaW5nIHRoZSBLTk5zIC0ga2VlcCB0aGUgZGlzdGFuY2VzCiMgICAgIGZpbmRLTk4oeCwgaz1rLCBCTlBBUkFNPUJOUEFSQU0sIEJQUEFSQU09QlBQQVJBTSwgZ2V0LmRpc3RhbmNlPWtlZXAuZGlzdGFuY2UpCiMgfQpgYGAKCgpgYGB7cn0KIyBzZXQuc2VlZCg0MikKIyBzaW0yLmtubiA8LSAuYnVpbGRLTk5HcmFwaCh4PXNpbTIucGNhJHhbLCBjKDE6MzApXSwgaz0yMSwgZD1OQSwgdHJhbnNwb3NlZD1UUlVFKQojIHNpbTIuZnIubGF5b3V0IDwtIGxheW91dF93aXRoX2ZyKHNpbTIua25uKQojIHBsb3Qoc2ltMi5rbm4sIGxheW91dD1zaW0yLmZyLmxheW91dCwgdmVydGV4LmZyYW1lLmNvbG9yPSdza3libHVlJywgdmVydGV4LmNvbG9yPSdza3libHVlJywgdmVydGV4LmxhYmVsLmNvbG9yPSdibGFjaycsCiMgICAgICB2ZXJ0ZXgubGFiZWwuZmFtaWx5PSdIZWx2ZXRpY2EnLCBlZGdlLmNvbG9yPSdncmV5NjAnLCB2ZXJ0ZXgubGFiZWwuY2V4PTAuOSwKIyAgICAgIHZlcnRleC5sYWJlbC5kaXN0PTEsIGVkZ2UuYXJyb3cuc2l6ZT0wLjIpCmBgYAoKX19OQl9fOiBTdG9yaW5nIHRoZSBkaXN0YW5jZXMgYXMgZWRnZSB3ZWlnaHRzIGlzIGZhciB0b28gbWVtb3J5IGludGVuc2l2ZS4gUGVyaGFwcyB3b3JraW5nIG9mZiB0aGUgYmFjayBvZiB0aGUgU2luZ2xlQ2VsbEV4cGVyaW1lbnQgd291bGQgYmUgYmV0dGVyIGZyb20gYSAKZGVzaWduIHBvaW50IG9mIHZpZXcuCgoKCgo=